古事連記帖

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

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

逆ジオコーディング、ザックリ言うと緯度経度といった座標から住所を割り出す仕組みの第三弾みたいなノリです。少し間が空いてしまいましたが…
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:高速道路なら追加ばかりですが、一般道に手を出した場合は全国で頻繁に新設・統廃合が起きるのでメンテコストが尋常じゃないはず…

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

逆ジオコーディング、ザックリ言うと緯度経度といった座標から住所を割り出す仕組みを作ります。

もともと(ほぼ)自分のために作っている、くるまに乗るときに使うアプリで住所情報を割り出して表示する仕組みを用意していましたが、これまでYahoo! Open Local Platformのリバースジオコーダを使っていました。

developer.yahoo.co.jp

このAPIは住所の他、いくつかの道路の路線名や愛称が返ってきます。道路名はあまり他のサービスにない便利な機能でしたが、最近できた高速道路*1を取得できないなど、情報が古かったりしました。
また、外部サービスであるがゆえにリクエストリミットの存在、商用利用ができないこともあり、ちゃんと作ってストアに公開するなど第三者に利用してもらうといったことが難しそうだなあと思いつついた中でしたが、改めて自分で作ってみようと思い立ちました。


YOLPで使ってきたものをできるだけ踏襲するために、必要な情報は以下の通り。

  • 住所情報
  • 道路情報

YOLPではそれぞれに読み仮名も用意されていましたし、作っていたアプリでは音声合成で住所を読み上げる際に使っていましたが、最近登場したVOICEPEAKがすごくよく、事前収録して使う方向にしたことで読み仮名の重要性が落ちたのでなくても問題なくなりました。

www.ah-soft.com


そして使うフレームワークASP.NET Coreで、住所や道路の情報を持つデータベースはMariaDBにしました。MSSQLも考えましたが汎用性とかを考えてMySQL(とその互換のMariaDB)にしました。
あとは今後のお勉強のためにDockerコンテナを作って運用することにしました。

住所情報

座標から住所を取り出すためにはそのためのデータが必要です。行政が提供しているもののなかで大きく2つ存在します。

国土交通省の位置参照情報
nlftp.mlit.go.jp

政府統計の境界データ
www.e-stat.go.jp

このうち国交省のデータは、住所大字・町丁単位でその地点の代表が点で存在します。政府統計のデータは大字・町丁単位で、その地点がポリゴンとして存在しています。
ちょっとわかりにくいかもしれませんが、図にするとこんな感じ。

これの違いとしては、今の座標が「どれだけその点が近いか」と「その点が接触しているか」のどちらで情報を得るかとなります。その点から一定の距離に含まれるものを表示するのでよければ前者を、取りたい情報が図形として存在するので確実に取りたい場合は後者を選ぶことになりますが、今回は後者を選びました。

ただ、政府統計の方は5年に一度の国勢調査で得られたものをもとに調査年の3年後に更新されるのに対し、国交省の方は1年に一回更新されるといった違いがあります。従って、例えば最近東京都の中央防波堤の一部が区に割り当てられて誕生した「大田区令和島」「江東区海の森」は国交省データにはありますが政府統計データには含まれていません*2

使うデータは決まりましたが、政府統計データに含まれるデータはGISで使う前提のため、シェープファイルとして存在しています。そのままデータベースに突っ込むわけにもいかないので、変換ツールを使いました。
github.com
使い方については特に言及しませんが、シェープファイルを読ませてあげれば良い感じにしてくれます。

MySQLに流す分にはこれで終わりでもいいんですが、MariaDBに流す場合ちょっと加工が必要です。
作られるテーブル名がファイル名で返されるので、それを置き換えたい場合は置き換えます。

CREATE TABLE `h27ka23` (gid serial,
を
CREATE TABLE `Aichi` (gid serial,
に置き換えるとか

MariaDBでは流すとエラーになるものを消したり、追加したりします。

ALTER TABLE `h27ka23` ADD COLUMN geom MULTIPOLYGON SRID 0 ;
を
ALTER TABLE `h27ka23` ADD COLUMN geom MULTIPOLYGON REF_SYSTEM_ID=0;
または、テーブル名を変えている場合は
ALTER TABLE `Aichi` ADD COLUMN geom MULTIPOLYGON REF_SYSTEM_ID=0;
に置き換えたり
各レコードの末尾
, 0, 'axis-order=long-lat'));
を
, 0));
にしたり('axis-order=long-lat'を認識してくれませんでした)

クエリファイル結構大きいので耐えられるエディター*3を使って置換するのがいいと思います

こうしてできあがったクエリデータですが、まあまあ大きなデータになります。これだけでも充分いけるんですが、日本全国の町丁までを総当たりさせるとめちゃくちゃ重たくなるので、都道府県単位(または市区町村単位)のポリゴンを用意するなどして、ある程度のしぼり込みをするといいかもしれないなあと思って、自分のではそうしています。もうちょっといいしぼり込みの仕方とか、もう全部ひっくるめた方がいいとかあれば教えて欲しいです。

まあまあデカい。01は北海道です

政府統計データにはもう一つ難点があり、住所以外にも河川や海などにも情報が存在します。シェープファイルQGISなどのビューアーで見ると「羽田沖水面」「多摩川河川敷」などといった名前が含まれています。

赤丸で囲ったのが本来住所として存在しない(はず)のデータ

QGISに読ませて加工してもいいんですが、まあまあ量があったりするので今回はめんどくさくてそのままにしました。

住所取得の準備はここまでです

道路情報

道路情報は国交省の位置参照情報に2つのものが存在します。ひとつは高速道路時系列データと、もうひとつは道路データです。

nlftp.mlit.go.jp
nlftp.mlit.go.jp

どちらもライン情報として提供されています。従って、点と扱いは同じく座標から近いかどうかで判断する必要があります。また、扱える情報にも差があり、前者は毎年更新されていますが高速道路のみ、後者は都道府県道や国道、高速道路がありますが、平成7年の情報でかなり古いです。
また、お世辞にもいい情報であるとは言えず、地図と重ねてみるとジャンクション構造が再現されていなかったりと、使いたい用途としては不適格な部分が多くあります。

茶色いのが高速道路時系列データ。わかりやすく太くしてある。地図は国土地理院地図。
紫色のが道路データ。わかりやすく太くしてある。地図は国土地理院地図。

持っている文字情報も政令路線名で書かれていたりするため、「第一東海自動車道」が「東名高速道路」であるとマッピングデータを作るか修正するかしないといけませんでした。

その他にもいくつか探してみたりしましたが、自分が思うほしい情報が取れるものがありませんでした*4


そこで、今回は自力で手作業して道路情報を作ることにしました。地図や航空写真を見ながらQGISでポリゴンデータを書いていく地道な作業です。
全国の都道府県道や国道をすべて描くのは非常に時間が掛かり、毎年何かしら変化が起きる可能性があることから作成せず、変化の起きにくい(ほぼ新規開通のみである)高速道路に絞って作成することにしました。

QGISのインストール方法や、国土地理院地図・航空写真を取り込む方法は国交省がまとめてpdfとして公開しているものを参考にしてください
https://nlftp.mlit.go.jp/ksj/other/QGIS_manual.pdf

地図、航空写真を「レイヤ」に取り込んだら準備完了です。「新規シェープファイルレイヤ」をクリックしてシェープファイルを作る準備をします。

表示されたウインドウに必要な情報を入れていきます。ファイル名は好きなものを、文字コードはとりあえずUTF-8でも。ジオメトリタイプは「ポリゴン」を選びます。追加次元や座標系については今回何も触れないでおきます。
フィールドはポリゴンに付加する文字情報です。最低限必要になるのは、「道路名」くらいでしょうか。必要に応じて「ID」や、例えば高速道路ナンバリングを表示したければそのためのフィールドを用意したりするといいかもしれません。
僕が用意したフィールドは「ID」、「道路名」「道路の愛称(名所となっている橋の名前とか、補足してつけておきたい名前など)」「ナンバリング」「道路のある都道府県名」です。

ここから実際にポリゴンを作っていきます。まず「編集モード切替」で編集モードにしてから「ポリゴン地物を追加」を押して描画できるようにします。

そうするとキャンバスに表示されるカーソルが変わります。表示されている地図にある道路の境界をクリックしていきながらポリゴンを作っていきます。

クリックして点を打っていくと赤く面ができていきます。地図は国土地理院の航空写真と地図を重ねたもの。

うまくできたら、右クリックしてポリゴンデータに情報を付加します。ウインドウが表示されるので、そこに先ほど用意したフィールドに情報を入れていきます。ここでOKを押すとデータができます。キャンセルを押すと作ったポリゴンも含めて削除されてしまうので、間違えて押さないようにしてください。

ほしい情報を入れていく感じ

OKを押した後、キャンバスに赤く表示されていたものが確定してオレンジ色になります。「レイヤスタイル」で好きな色に変更ができるので、例えば不透明度を下げて地図が見えるようにするとよいと思います。

不透明度を下げた感じ

これをどんどん繰り返していき、道路情報を作っていきます。
まだまだ作りかけですが、僕が作った分としては以下の画像の通りです。

まず首都高をほぼ作り上げるところから始め、自分が通る頻度の高い(高そうな)道路を中心に郊外へ延ばすように作りました。GPSで取れる精度のことも考えて、中央分離帯がかなり広くない限り上下線の区別はせず、一方でジャンクションは細かく、ランプウェイは接続先の路線名を名乗るようにするなどしてみました。

伊勢原ジャンクション

フィールド情報は以下の通りにしていて、ポリゴンにナンバリングをつけられるので、例えば同じ道路名でも区間でナンバリングが違うところ*5も割と正確に出せるようになります。

東名の右ルート・左ルートとかは愛称として入れました

かなり地味すぎてマウスだとしんどくなってきたので、ペンタブレットを買いました。Intuos ProのSサイズです。ペンのボタンを全部無効化して、タブレット本体のボタンにキャンバスの拡大縮小や移動、右クリックのショートカットを割り当ててあげるとすごく効率よく描くことができます。

ある程度書き終えたら、「レイヤ」にある作成したシェープファイルのレイヤーを右クリックし、「エクスポート」から「新規ファイルに地物を保存」を選びます。

表示されたウインドウで「形式」は扱いやすいものを選びます。僕はMariaDBへ流し込むクエリに変換するコードを別途組んでいるので、扱いやすい「GeoJSON」を選んでいます。ファイル名に名前を入れ、下にある「保存されたファイルを地図に追加する」のチェックを外し「OK」を押します。

GeoJSONで出力したデータをクエリファイルに変換します。中身はザックリ言えばJSONなので、構造さえわかればあとは住所のクエリを参考にして変換ツールを組めるかと思います。
僕が用意したフィールド情報で作るとこんなデータができあがります。

gist.github.com
中身はView Rawで見れます

データは"features"配列の中に入ってくるので、その中に含まれるものをかいつまんで変換する感じです。

できあがったクエリは以下の感じです。住所データと似せて作ってあります。
gist.github.com

これでひとまず準備ができました。
準備だけですごく時間が掛かってしまったので、サーバーの実装については以下の別記事をご参照ください。

ayano.hateblo.jp

*1:東名高速道路はちらっと追加されたりはしましたが

*2:誕生は2020年のため、次に更新されるであろう2023年にも含まれるか怪しいところ

*3:Visual Studio Codeが割と頑張れる

*4:たぶん地図会社の法人向けAPIなどには存在していると思います。趣味で使うには非常にしんどい

*5:中央道や圏央道など

Honda Total Care 車内Wi-Fi がフィット Modulo X にもやってきた

先日乗り換えたフィット e:HEV Modulo X は、他の年次改良後のフィットと違いカーナビの設定に Honda CONNECT ディスプレーを選択することができず、ディーラーオプションのカーナビのみが利用できるようになっています。

ayano.hateblo.jp

www.honda.co.jp

f:id:ChiiAyano:20211007155422p:plain
Honda CONNECT for Gathers は標準搭載だけど…

この場合、Honda Total Care でできるサービスのうち、ディスプレーと連動して動作する自動地図更新、アプリセンター、車内Wi-Fi については DOP ナビでは利用ができないことになっていましたが、つい 1 週間前に車内Wi-Fi については利用が可能になる旨アナウンスがありました。
そして本日 10 月 7 日にサービス開始となりました。

以下の内容は、2021 年 6 月に発表された年次改良後の GR1/GR2/GR3/GR4/GR5/GR6/GR7/GR8 フィット (DOP ナビ搭載車、Modulo X 含む) と、おそらくヴェゼル (DOP ナビ搭載車、RV3/RV4/RV5/RV6) 向けの話ですが、Honda CONNECT ディスプレー搭載車であるフィット、ヴェゼルシビック、Honda e も同じかもしれないです。

Honda Total Care アプリが 10 月 7 日付けで最新版が出ているので、これが必須です。というか、Honda CONNECT ディスプレーと違い、DOP ナビからは Wi-Fi のオン・オフも、データパックの購入もできないので、すべてアプリ経由でおこないます。
アプリを起動すると、その辺に「車内Wi-Fi」というボタンが出てくるので押します。
f:id:ChiiAyano:20211007162137p:plain

そしてなんかチュートリアルが出てきて
f:id:ChiiAyano:20211007162157p:plain

この画面にきます。既にパックを購入済みの状態だったのでこんな画面ですが、おそらく購入してなければまた違う画面かも。
f:id:ChiiAyano:20211007162252p:plain

ここで「申し込む」を押すとパック購入画面になるので、適当に買います。初手は 1 GB が無料なのでとりあえず押しちゃえばいいと思います。ちなみに 3 ヶ月後の月末まで有効です。
あとは車に乗り込み、パワーモードがオフモード、アクセサリーモード以外のとき(要するにオンモード*1とパワーシステム起動時*2)に「車内Wi-Fiを起動する」の右側のトグルスイッチをオンにすれば OK です。車内の通信端末で AP が立てられます。
SSID/Pass 設定」をするときも同様にオンモード、パワーシステム起動時に設定ができます。ちなみにこの画面はオフモード、アクセサリモードではエラーが表示され見ることができません。
f:id:ChiiAyano:20211007164736p:plain

なお、現時点では公式サイトの「使い方ガイド」では DOP ナビのときの使い方が一切書かれていません。こんなページは用意されていますが画面が Honda CONNECT ディスプレーのなのでほぼ意味がないです。 いつの間にか正しい手順になってました 🤔
www.honda.co.jp

Wi-Fi をオンにすると、どこからともなく AP が見えるようになります。(SSID変えてしまったのでわかりづらいですがw)
f:id:ChiiAyano:20211007173909p:plain

バックボーンはソフトバンクです*3。計測地点がそんな繁華街って訳ではないですがまあこんくらいでれば…とは思いました。
f:id:ChiiAyano:20211007174235p:plain


まだ始まってすぐであんまり遊べてませんが、ガジェッターな人にとってはおいしくないかなあと思ったりはしました。 だいたいみんなたくさん回線持ってるでしょ
330 円 / 1 GB でまあまあ普通な感じではあるものの、2 GB 買おうが 5GB 買おうが単価が安くならないのがちょっと残念。さらにどれだけ買おうが購入時点から 3 ヶ月後の月末まで有効なので、継ぎ足しができないです。
週末に乗るときにどれだけ使うか次第ですが、テザリングできる端末やモバイルルーターなどを持っている場合はそっち使うのがよさそうです。一方で、端末を多く持っていないとか、低容量プランで頑張ってる人には使えるかなあ…とは思いました(残念ながら僕は前者の人なのであまり…)

ちなみに今なら 1 GB パックを購入すると抽選で 100 人に 10 GB が年末あたりに送られるらしいですよ。

*1:ブレーキペダルを踏まずパワースイッチ (ガソリン車は「ENGINE START/STOP」) を 2 回押したとき

*2:ブレーキペダルを踏んでパワースイッチ (ガソリン車は「ENGINE START/STOP」) を押したとき

*3:インターナビ初期の通信端末がWILLCOMだったりなのでその流れをくんでいる感じ

モデルナワクチンを2回打ち終わりました

6月28日、および7月26日にそれぞれモデルナワクチンを無事打ち終わりました。これで一安心…とは言えない状況になっているなか、これからも引き締めて生きねばと思っています。

1回目、2回目ともに経験した副反応、その経過を事実だけ記載しておきます。この内容は僕が経験したものであり、すべての人が同じように経験するものではなく、またこの内容でワクチンを打つべき・打たないべきと主張するものではありません。

1回目の接種

筋肉注射は初めてだったので若干不安でしたが、打ったかどうかすらわからないまま終了しました。打っている様子は見ない人なので本当に打たれたんだろうか?と別方面で不安になっていましたが、帰り道に副反応が出始めたので杞憂に終わりました。
打った腕が割とすぐに違和感が出て、夜には痛みになり、腕が水平より上に上がらず、その時点で激痛が走る…といった状況で、服を脱ぐのも着るのも苦労するのでもう服着なければいいのでは…!?と思ったりするくらいでした。
さらに、ワクチン接種する数日前から接種した反対側の首を寝違えてしまい、どっち向きに寝てもしんどいというハードモードな展開。首の痛みは耐えかねてシップを買いに行くレベルでした。
痛みが落ち着いたのは副反応も寝違えも同時期で接種から3日目の夜あたりくらい。その前日にやや微熱を出した以外で目立った副反応は起きてない感じです。

2回目の接種

2回目は注射の痛みがややありましたが、蚊に刺されているのを認知して意識したときの痛みよりは痛くない印象でした。
その日の夜にめまいのような感じになった後一気に具合が悪くなっていきました。
2日目の明朝に微熱と倦怠感が現れ、そこからどんどん熱が上がっていきました。腕は1回目に比べると痛みはそこまで起きず(とはいえ打った方を向いて寝ようとすると痛い)、あっさり引いていったと記憶しています。
昼頃に体温が38.6℃を記録し、この時点で解熱剤を飲んでしばらく寝ていましたが、夕方に計測したとき、暗がりだったのでよく見えず「35.4℃かな…?」と思って明かりをつけたら 39.4℃ で、38.0℃以上ですらここ数年経験してなかったのに、まさかの39.0℃超えでかなりビックリしています。
熱は3日目も続き、昼頃までは倦怠感が残ったままで、発熱は3日目深夜まで続きました。4日目の朝にようやく平熱まで戻ってきた感じでした。
解熱剤と他にポカリスエットがお友達になるとは思わず、ポカリスエットを飲んだ途端に汗がぶわっと出るといった具合で水分の足りなさを実感したりと、もっと積極的に取るべきかもしれないと感じています。3日間で1.5Lペットボトルを1本半あけました。それ以外には熱さまシートも気持ちには効果的でした。


前情報通り、2回目の副反応が強く出る形で経過しましたが、実際に感染して発症していたらもっとつらいという話もあるので、これで済んでよかったのかもしれません。

フィット e:HEV Modulo X に乗り換えた

f:id:ChiiAyano:20210626180644p:plain
フィット e:HEV Modulo X

本日、新しい車に乗り換えました!フィット e:HEV Modulo X です!

www.honda.co.jp

思えば、2020 年 1 月の東京オートサロンで見た「FIT Modulo X Concept」にすごく刺激を受けたのがすごく記憶に残っています。

www.honda.co.jp
car.watch.impress.co.jp

まだこのときは市販化されるかはわからなかったんですが、もし販売されるならなんとしてでも乗り換えると意気込んでいました。


時は過ぎ、12月にはプロトタイプのサーキット試乗動画が公開され、市販化秒読みの段階になり、そして 2021 年 3 月

営業さんから「Modulo X 出ますよ」と電話が。

4 月に注文が可能になり、成約。このときは 7 月上旬が納期になりそうというざっくりとした回答でした。

f:id:ChiiAyano:20210626182740p:plain
みつもり

同時期、ヴェゼルがフルモデルチェンジする流れの中、半導体不足などの影響で納期が延びているなどといった話をちらほら聞きながら、自分の車は大丈夫だろうか…?などと不安になりつつ、フィットの年次改良および Modulo X の発表の前後に営業さんから「6月末には納車できそう」とのお話をもらったので、どうにか遅れず済むかなあという感じでした。


そしてきょうが納車日。これまで乗ってきたビビッドなブルーのフィット 3 とは別れを告げ、白く輝くフィット e:HEV Modulo X へと乗り換えました。

f:id:ChiiAyano:20210626180305p:plain
在りし日の姿
f:id:ChiiAyano:20210626181301p:plain
別角度から
f:id:ChiiAyano:20210626181504p:plain
後ろ姿
f:id:ChiiAyano:20210626182947p:plain
後ろ姿を別角度から。ルーフはブラックなのです

先代が i-DCD で、どちらかというとエンジン主体だったときと比べると、すごくスムーズで、多少なりあった変速ショックも当然ながらないのですごく快適です。Modulo X の特徴でもある座席はノーマルよりも座面はかたいです。そして材質もあって滑らず、がっちりホールドされている感じで安心感があります。一方で、i-DCD のときはモーター駆動になるタイミングを自分で (アクセルワークを駆使して) 制御できたりしてたのが、e:HEV だとなかなかうまくやれないので、もしかすると先代で記録した生涯燃費 23 km/L よりも下がるかもしれないなあとは思ったりはしています。
乗り味に関しては、めっちゃ素人なのでよくわかりませんw かたい感じはありますが、先代も同じくらいあったかなあと思ったりしたくらいなので。しっかりまっすぐ走ってくれる感じはするので安心して走れるかもなーとは思っています。先代に比べるとすごい静粛性が上がったので、スピーカーの音がすごくよく聞こえるのはうれしいポイントかも。

先代同様、いろいろ試行錯誤しながらドライブを楽しみたいところです。


納車後につけたサードパーティのグッズを Amazon アフィにして載せました。よければどうぞ。

小物入れとしてめっちゃ便利です。粘着テープを使うので抵抗ある人もいるかも。

スマホ置き場になりました。テーブルコンソールのレールに固定するタイプなので取り外しも楽です。

PCのスリープに失敗するようになった その後

以前、PC をスリープすると、復帰に失敗して強制電源断状態からの起動になるというお話をしました。
ayano.hateblo.jp

結論から言うと、未だに原因は不明ではあります。

メインストレージに使っている MX300 の換装を目的に Crucial P5 を購入し、データ移行はせず新たに構築し直しをしています。

f:id:ChiiAyano:20210417114751j:plain
換装した Crucial P5 と、WLAN NIC (これはおまけ)

まっさらな状態でも再現してしまうのか試してみたところ、予想に反しスリープおよびスリープからの復帰に成功しました。…一体どういうことなのか…。
前までの何かが原因でスリープからの復帰に失敗していると考えると、まだ油断ならない部分はありますが、ひとまず一安心といったところです。元の構成になおす過程でトリガーを見つける可能性はあるので、そのときまた記事に残してみます。


ちなみに、今回換装した Crucial P5 で初めてデスクトップ PC に NVMe を挿しました。熱に関しては Surface Laptop で重々承知してはいましたが、マザーボード付属のヒートシンクをつけてもなおアイドル時の温度が 54 ℃ と他の SATA SSD に比べてもアツアツです🌡
速度に関しては普段使い程度だと体感ではわからないんじゃないかなあとは思います。割と自己満足の部類のイメージ。

f:id:ChiiAyano:20210417233752p:plain
割と速そうには見えます

換装にあたり、グラフィックボードの真下に M.2 スロットがあったり、WLAN NIC を PCIe x1 スロットに挿そうと思ったらグラフィックボードで干渉していて PCIe x16 スロットに挿したりと、割と苦労しました。これだから microATX は…。


以下 Amazon アフィリンクです