VWork バイブコーディングフレームワーク

民泊の可否を住所から調べる:用途地域GISと上乗せ条例を1ファイルのPHPで引く設計

住所を入れると、その場所の用途地域と、その自治体の民泊の上乗せ条例を返すシステムを作りました。作る過程で、データの選び方とライセンスの確認に一番時間がかかりました。その記録です。

デモ: https://kurage.exbridge.jp/kminpaku.php/

何が問題だったか

民泊には制度が3つあります。

  1. 住宅宿泊事業法(民泊新法)… 届出。年間180日まで
  2. 国家戦略特別区域法(特区民泊)… 認定。区域が限られる
  3. 旅館業法(簡易宿所)… 許可。日数の上限なし

窓口も条件も別々で、住所から横断して引ける場所がありません。これは国会でも指摘されています。参議院の小西洋之議員が「旅館業法における簡易宿所の課題に関する質問主意書」で、こう書いています。

観光庁は、住宅宿泊事業法、国家戦略特別区域法及び旅館業法に基づく三形態の宿泊施設情報を一元的に管理・把握するシステムの構築計画を公表している。しかしながら、現行の旅館業法には住宅宿泊事業法に関する規定や情報連携の根拠が存在しないため、当該システムによる情報共有が機能不全に陥る懸念がある。

判定に必要なのは2つです。その住所の用途地域と、その自治体の上乗せ条例(住宅宿泊事業法18条で、都道府県や保健所設置市が条例で区域と期間を制限できる)。

用途地域のデータを間違えて選びかけた

最初に国土数値情報の用途地域データ(A29 第2.1版)を使おうとしました。全国が1つの形式でそろっていて、都道府県ごとのzipで配られています。

ところが配布ページにこう書いてあります。

国土数値情報(用途地域)データの利用にあたっては、国土数値情報ダウンロードサイトコンテンツ利用規約のほか、各市区町村で定められた利用条件を必ず遵守するようにしてください。

リンク先のPDFに、自治体ごとの条件が1,213行の表で載っていました。構造化して数えた結果がこれです。

条件 件数
オープンデータ公開可 960
条件を付して公開可(有償利用不可・再配信不可など) 176
公開不可 56
回答なし 15
資料の提供なし 6

有償の製品に入れられるのは1,012(83.4%)でした。しかも入れられない側に、大阪市(公開不可)、京都市・大田区・渋谷区・江東区・練馬区(有償利用不可)、豊島区・八王子市・姫路市・那覇市(公開不可)が並びます。民泊で一番肝心な自治体が全部そこにいました。

このまま作っていたら、有償で売った時点で規約違反です。表をパースするスクリプトを書いて、条例のある63自治体と突き合わせたところ、13自治体が該当しました。

代わりに使ったデータ

国土交通省 都市局の「都市計画決定GISデータ(全国)」に切り替えました。

https://www.mlit.go.jp/toshi/tosiko/toshi_tosiko_tk_000087.html

属性は YoutoName(用途地域名)、YoutoCodeBCR(建蔽率)、FAR(容積率)、CitynameCitycode です。

ただし使用許諾条件にこう書かれているので、画面にそのまま出しています。

掲載データは、建築確認申請や不動産重要事項説明等の手続に用いることを保証するものではなく、参考情報として利用を想定しています。

1.9GBを57MBにする

全国の用途地域は102,159面あります。GeoJSONのままSQLiteに入れたら1.9GBになりました。売り物として配れるサイズではありません。

2段階で詰めました。

  1. ダグラス・ポイカーで1mまで間引く。 都市計画総括図をデジタイズしたデータなので、直線上に冗長な点が並んでいます。点が38%まで減りました
  2. 座標を int32 に詰める。 経度・緯度を1e-7度単位の整数にすると1点8バイトです。精度は約1cm。JSONだと1点39バイトかかっていました

結果、全国で57MB。都道府県ごとに1ファイルに分けたので、必要な県だけ置けば数MBです。

面の形式は自前で決めました。

[面の数 u16] ( [輪の数 u16] ( [点の数 u32] (lon,lat の int32 × 点数) ) )

PHP側は unpack('l*', ...) で読んで、交差数判定(ray casting)で内外を見ます。外側の輪に入っていて、穴の輪に入っていなければ中、という素直な実装です。

本番に上げて初めて落ちた

外接矩形の絞り込みに SQLite の R*Tree を使っていました。手元では問題なく動きます。本番(heteml)に上げたら、判定がこれで落ちました。

PDOException: SQLSTATE[HY000]: General error: 1 no such module: rtree

heteml の PHP に同梱された SQLite には rtree モジュールが入っていません。買い切りで配る以上、置かれる先の環境も選べません。

外接矩形を普通の列(minx, maxx, miny, maxy)で持ち、minx に索引を張る形に変えました。都道府県ごとに1ファイルで最大でも1万面ほどなので、これで十分速く、本番で0.1秒前後です。

R*Tree を使わない判断は、性能を落とすのではなく、動く環境の幅を広げる判断でした。

全国にして初めて出た不具合

東京都と大阪府の2府県で作っている間は気づきませんでした。全国にしたら京都府の住所が全部「未収録」になります。

原因は住所から都道府県名を切り出す正規表現でした。

preg_match('/^(.+?[都道府県])/u', $title, $m)

最短一致なので、「京都府京都市中京区」は「京都」で止まります(「京」+「都」)。「東京都」は「東京」+「都」で正しく取れるため、東京と大阪では起きません。47の名前で照合する形に直しました。

同じ取りこぼしを次に出さないよう、分かっている住所8件で当たりを確かめるスクリプトを作って、京都の件も見張り対象に入れました。データベースを作り直すたびに流します。

ok 東京都大田区羽田1-1-1         近隣商業地域 / 条例あり
ok 東京都世田谷区成城6-1-1        第1種中高層住居専用地域 / 条例あり
ok 京都市中京区寺町通御池上る…      商業地域 / 条例あり
ok 福岡市中央区天神1-8-1         商業地域 / 条例なし
8/8 一致

条例のPDFを読む

上乗せ条例は2つの出どころを分けました。

一覧は一般財団法人 地方自治研究機構の追跡ページから。令和8年4月1日時点で63自治体です。制限の中身は観光庁の「民泊の実施制限に関する地方公共団体の条例のとりまとめについて」(PDF 61ページ)から。

国のとりまとめは令和3年4月1日時点・58自治体で止まっています。その後に墨田区・葛飾区(令和7年12月)、江戸川区・高槻市(令和8年3月)が制定しました。古い表だけを見せると、規制がある区を「制限なし」と読ませてしまいます。そこで一覧と中身で出どころを分け、画面には必ず両方の時点を出すことにしました。

PDFの表は、見出し(区域・期間)が欄の縦中央に置かれ、本文が見出し行の上下にまたがって折り返します。pdftotext -layout で流して行単位で割ると「の地域」「い日を除く期間」のような切れ端になります。

         知事が指定する小中学校等の敷地の出入口の周囲100メートル
    区域   の地域

3段階で戻しました。

  1. 右端の「趣旨」欄を落とす。見出し行の「趣旨」の位置(48)と本文行で趣旨が始まる位置(41)はずれるので、本文行から実測した最小値で切る
  2. 見出し列と本文列を、本文の書き出し位置で分ける
  3. 本文を順に溜め、見出しが来たらその塊の名前にする。次の見出しか空行、または本文のない①②③の行で塊を閉じる

58自治体中49で区域と期間の対が取れました。取れなかった9件は「このとりまとめには載っていません」と表示します。条例の文は要約も解釈もせず、原文のまま出します。 解釈を挟むと、営業できない場所を「できる」と読ませる事故になります。

判定が返すもの

大阪府大阪市中央区本町一丁目1番1号
用途地域: 商業地域(建蔽率 80% / 容積率 800%)
上乗せ条例: 大阪市住宅宿泊事業の適正な運営の確保に関する条例
  区域: 住居専用地域(幅員4メートル以上の道路に接している場合を除く。)
  期間: 全ての期間
  区域: 小学校の敷地の周囲100メートル以内の区域

住居専用系の用途地域だったときは「条例で区域・期間の制限が置かれやすい区分です」と添えます。収録していない自治体は「区域外」ではなく「未収録」と書いて区別します。ここを混ぜると、データが無いだけの場所を「制限なし」と読ませます。

使った構成

ソースコードと更新用スクリプトを同梱したオンプレミス版も置いています。

https://kappstore.exbridge.jp/app.php?id=c36227d2b62b8517

まとめ

作業時間の内訳で言うと、判定ロジックよりデータの選定とライセンスの確認のほうが長くかかりました。全国がそろっているように見えるデータでも、自治体ごとに条件が違うことがあります。有償で配るなら、そこを数えてから作り始めるべきでした。

もう1つは、2府県で動いたものが全国で動くとは限らないこと、手元で動いたものが本番で動くとは限らないことです。どちらも実際に踏みました。分かっている入力で当たりを確かめるスクリプトを、本番URLに対しても流せる形にしておくと、次の作り直しで同じ場所を踏みません。