Claude Media
用途地域と容積率をAPIで引く — Claude Codeで敷地の法規メモ

用途地域と容積率をAPIで引く — Claude Codeで敷地の法規メモ

不動産情報ライブラリのXKT002で、緯度経度から用途地域・容積率・建蔽率を引き、敷地ごとの法規メモにする手順。タイル変換、点と面の判定、取れない項目の扱い、出典表示までをClaude Codeに任せます。

敷地の用途地域・容積率・建蔽率は、国土交通省の不動産情報ライブラリのAPI「XKT002」で取れます。緯度経度から地図タイルの座標を計算し、返ってきた区域の図形に敷地の点が入るかを判定すれば、敷地ごとの法規メモを自動で作れます。Claude Codeには、この変換と判定を含むスクリプトを書かせて検算させます。

取引価格の側は不動産情報ライブラリAPIで取引価格・地価を取る記事にあります。XKT002は同じキーで呼べますが、市区町村コードでなくタイル座標で指定する作りで、扱い方が違います。この記事はその違いと、法規メモにするときの線引きを扱います。

XKT002は何を返すか

XKT002は「都市計画決定GISデータ(用途地域)API」です。原典は令和7年度の都市計画決定GISデータの「用途地域」で、パラメータは4つ、すべて必須です。

パラメータ内容
response_format内容geojson か pbf
z内容ズームレベル。11(市)から15(詳細)まで
x内容タイル座標のX値
y内容タイル座標のY値

地名や住所で絞る引数はありません。引けるのは「このタイルに重なる用途地域の区域」だけです。法規メモに使う出力項目は次のとおりです。

タグ名内容データ例
use_area_ja内容用途地域名データ例準工業地域
u_floor_area_ratio_ja内容容積率データ例300%
u_building_coverage_ratio_ja内容建蔽率データ例80%
city_name / city_code内容市区町村名 / コードデータ例神戸市 / 28100
decision_date内容区域設定年月日(最終告示日)データ例例は空欄
notice_number内容告示番号(最終)データ例例は空欄
first_decision_date内容当初決定日データ例例は空欄

容積率と建蔽率はどちらも文字列型で、「300%」のように単位つきで返ります。そのまま掛け算に使えないので、数値へ直す工程が要ります。

認証は取引価格のAPIと同じです。キーをリクエストヘッダー Ocp-Apim-Subscription-Key に載せ、ブラウザからは送りません。データが無いタイルでも、タイルを指定するAPIはHTTPステータス200で空の配列を返します。404でなく空配列なので、「用途地域が無い」と「呼び出しに失敗した」を取り違えない処理が必要です。

緯度経度をタイル座標にする

敷地の位置は住所で持っていることが多く、先に緯度経度へ直します。住所から座標を出す手順は国土地理院の住所検索APIの記事にあります。

タイル座標への変換は、マニュアルが参照先に挙げる国土地理院のXYZ方式です。次の式で、緯度経度とズームレベルからx・yを出せます。

import math
 
def tile(lat, lon, z):
    n = 2 ** z
    x = int((lon + 180) / 360 * n)
    y = int((1 - math.asinh(math.tan(math.radians(lat))) / math.pi) / 2 * n)
    return x, y

この式が正しいかは、マニュアルの使用例で検算できます。XKT002の例はz=14、x=14339、y=6505で、出力例の都市名は神戸市です。逆算すると、このタイルの中心は北緯34.71度、東経135.08度付近になり、神戸市の中心部と合います。座標の取り違え(緯度と経度の順、yの上下)があるとここで大きく外れるため、Claude Codeには最初にこの逆算をさせます。

tile(lat, lon, z) を書いたら、XKT002のマニュアル例 z=14, x=14339, y=6505 を
緯度経度に逆算して表示して。神戸市付近に出なければ式を直す。

ズームレベルは、敷地の判定なら15で足ります。値が大きいほどカバーする範囲は狭くなり、タイルの辺が短くなります。

点がどの区域に入るかを判定する

タイルで返るのは、そのタイルに重なる区域のGeoJSONです。タイルの端に近い敷地では、隣の区域の図形も混ざります。敷地の点が入っている図形を選ぶのは、こちらの仕事です。

次のスクリプトは、点と図形の内外判定を標準ライブラリだけで書いた例です。GeoJSONの座標は経度、緯度の順なので、判定には (lon, lat) を渡します。

import gzip
import json
import os
import time
import urllib.parse
import urllib.request
 
BASE = "https://www.reinfolib.mlit.go.jp/ex-api/external/"
Z = 15
 
 
def fetch(api, x, y, z=Z):
    q = urllib.parse.urlencode(
        {"response_format": "geojson", "z": z, "x": x, "y": y})
    req = urllib.request.Request(f"{BASE}{api}?{q}")
    req.add_header("Ocp-Apim-Subscription-Key", os.environ["REINFOLIB_API_KEY"])
    with urllib.request.urlopen(req) as res:
        raw = res.read()
    if raw[:2] == b"\x1f\x8b":  # gzipの先頭バイト
        raw = gzip.decompress(raw)
    return json.loads(raw)
 
 
def in_ring(px, py, ring):
    inside = False
    for (x1, y1), (x2, y2) in zip(ring, ring[1:] + ring[:1]):
        if (y1 > py) != (y2 > py):
            if px < (x2 - x1) * (py - y1) / (y2 - y1) + x1:
                inside = not inside
    return inside
 
 
def in_polygon(px, py, rings):
    # rings[0] が外周、以降は穴
    return in_ring(px, py, rings[0]) and not any(
        in_ring(px, py, r) for r in rings[1:])
 
 
def hits(fc, lat, lon):
    if not isinstance(fc, dict):
        return []  # 該当なしは空の配列で返る
    out = []
    for f in fc.get("features", []):
        g = f["geometry"]
        polys = ([g["coordinates"]] if g["type"] == "Polygon" else
                 g["coordinates"] if g["type"] == "MultiPolygon" else [])
        if any(in_polygon(lon, lat, p) for p in polys):
            out.append(f["properties"])
    return out

呼び出し側では、敷地ごとにタイルを引いて hits() に通し、必要な項目だけ残します。連続リクエストは避けるよう公式が求めているので、敷地の間に time.sleep(1) を入れます。

KEYS = ("use_area_ja", "u_floor_area_ratio_ja",
        "u_building_coverage_ratio_ja", "city_name",
        "decision_date", "notice_number")
 
 
def lookup(name, lat, lon):
    x, y = tile(lat, lon, Z)
    found = hits(fetch("XKT002", x, y), lat, lon)
    time.sleep(1)
    return {"name": name, "tile": f"{Z}/{x}/{y}",
            "zones": [{k: p.get(k) for k in KEYS} for p in found]}

内外判定は、角の数が多くても動く偶奇則(点から横に線を伸ばして辺と交わる回数を数える)です。外周の中にあって穴の中にない点だけを「入っている」とします。

結果が0件・複数件のとき

zones が空なら、そのタイルに区域が無いか、点が用途地域の指定のない場所にあります。2件以上のときは、点が区域の境界の近くか、図形が重なっています。どちらも自動で1件に決めず、メモに「複数」と書いて人が確かめる扱いにします。境界の上の点は、丸め誤差でどちらにも入りうるためです。

初回は応答の形を確かめる

XKT002のページの出力例欄は、GeoJSONの見本が空欄です。出力表のタグ名がGeoJSONの properties のキーに対応する、という前提でスクリプトを書いているので、最初に1件だけ実物で確かめます。

敷地1か所だけXKT002を呼び、応答のトップレベルのキーと、features[0] の
properties のキー一覧、geometry の type をそのまま表示して。
メモの作成には進まないで。

properties のキー名が出力表と違えば KEYS を直し、geometry の type がPolygonとMultiPolygon以外なら hits() に分岐を足します。座標が経度、緯度の順かもここで見ます。

敷地ごとの法規メモにする

lookup() の結果は、そのまま法規メモの行にできます。メモに入れる列は、返ってきた値と取得の経緯に絞ります。

列中身
敷地名・座標中身入力。座標の出どころ(住所検索など)も添える
用途地域中身use_area_ja
容積率・建蔽率中身返ってきた文字列のまま
区域の設定日・告示番号中身decision_date、notice_number
取得タイル中身15/x/y。再現できるようにする
備考中身複数該当・0件・要確認の印

容積率と建蔽率は、メモでは文字列のまま持つのが安全です。数値へ直して計算に回す場合も、元の文字列の列は残します。「300%」を 300 に直す処理で、単位違いや欠損が見逃されるのを防げます。

このAPIで取れないもの

出力項目に、前面道路の幅、角地、敷地の形、高さ制限などの欄はありません。ここから出せるのは、図面に載った用途地域の区域が持つ指定値までです。個別の敷地で使える容積率や建蔽率が、指定値のままになるかどうかは、このデータでは決まりません。

そのため、メモの見出しは「指定値」とし、「適用値」とは書かない形にします。実際の適用を判断する側の根拠は条文です。条文を施行令・規則まで辿る方法はe-Gov法令APIで建築基準法の条文を辿る記事にまとめています。

防火地域・地区計画・高度利用地区も同じ形で引く

都市計画情報のAPIは、パラメータと呼び方がXKT002と共通です。response_format・z・x・y の4つで、ズームレベルは11から15です。敷地の周辺条件を足すなら、次のAPIが同じスクリプトで呼べます。

IDAPI名主な出力項目
XKT014API名防火・準防火地域主な出力項目fire_prevention_ja(防火・準防火地域名)、city_name、notice_number
XKT023API名地区計画主な出力項目plan_name(計画名)、plan_type_ja、city_name
XKT024API名高度利用地区主な出力項目advanced_name(高度名称)、advanced_type_ja、city_name

呼び出すときに変わるのはAPI名の部分だけで、fetch("XKT014", x, y) の形で済みます。ただし出力項目の名前が違い、市区町村の項目も city_name(XKT014は city_code も)、group_code などAPIによって異なります。hits() は共通で使えても、KEYS はAPIごとに分けます。

XKT014のマニュアル例はz=15、x=29096、y=12948で、横須賀市(神奈川県)の準防火地域です。XKT023とXKT024の例は東京都千代田区の地区計画と高度利用地区です。該当がある敷地では、メモの備考に「地区計画あり(計画名)」のように計画名を書き写します。地区計画の中身(制限の詳細)はこのAPIの項目にはないため、計画名から当該区域の資料を探す手がかりにとどめます。

法規メモの位置づけと出典表示

不動産情報ライブラリの利用規約は、コンテンツを不動産取引時の重要事項説明や建築確認申請などの手続に用いることを保証するものではなく、参考情報としての利用を想定している、と書いています。法規メモは「当たりをつける資料」であって、手続の根拠書類ではありません。

そのほかの規約上の決まりのうち、メモに関わるものは次のとおりです。

  • コンテンツを利用するときは出典を記載する。例は「出典:国土交通省 不動産情報ライブラリ」と、サイトまたは該当ページのURL
  • 編集・加工して使うときは、出典とは別に、利用したコンテンツの名称と加工したことを書く。「(国土交通省)をもとに○○作成」の形が例示されている
  • コンテンツによって、作成・更新時点が違う。内容が最新でないことがあるとも書かれている
  • 都市計画決定GISデータの全国データは、規約の別項で、国が権利を持つため別の利用ルールに従うコンテンツとして挙げられている

最後の項目は、メモの再配布や公開を考えるときに効きます。社内メモなら出典の一文で足りますが、外へ出すなら、都市計画決定GISデータ側の利用ルールも見てから決めます。メモの末尾には、スクリプトから次の一文を出力させます。

出典: 国土交通省 不動産情報ライブラリ(https://www.reinfolib.mlit.go.jp/)
「都市計画決定GISデータ(用途地域)」(国土交通省)をもとに加工して作成

「不動産情報ライブラリ」という名称を使った製品・サービス・アプリ名は、国土交通省が認めた場合を除いて認められていません。社内ツールにこの名前を付けないよう、ツールの命名の段階で避けます。

CLAUDE.mdに書いておく取得ルール

同じ注意を毎回指示せずに済むよう、プロジェクトのCLAUDE.mdに置いておきます。次は書き方の一例です。

## 不動産情報ライブラリ 都市計画API(XKT002ほか)
- キーは環境変数 REINFOLIB_API_KEY から読む。値を出力・コミットしない
- 位置はタイル(z=15)で指定する。tile() は z=14,x=14339,y=6505 の逆算で検算する
- 判定は経度,緯度の順。0件・複数件は自動で決めず「要確認」と書く
- 容積率・建蔽率は文字列のまま保持し、列の見出しは「指定値」とする
- リクエストは1秒以上あけ、ブラウザからは送らない
- 出力するメモには出典と「加工して作成」の一文を必ず付ける
- メモは参考情報。手続の根拠として扱わない

Claude Codeにスクリプトを書かせるときは、このルールを読ませたうえで、敷地3か所ほどの小さい入力から始めます。先に出力を目で確かめる敷地を決めておくと、判定の誤りに気づきやすくなります。

よくある質問

住所だけで用途地域を引けるか

XKT002には住所や地名の引数がありません。住所は先に緯度経度へ直し、その点が入るタイルを指定します。

pbfで取ると何が違うか

pbf はバイナリベクトルタイルで、地図ライブラリやQGISで描画するための形式です。公式のPythonとJavaScriptのサンプルはGeoJSON用で、PBFは出力できないと明記されています。法規メモの用途ならgeojsonを選びます。

まとめ

用途地域・容積率・建蔽率を引く流れは、住所から緯度経度を得て、z=15のタイルを指定してXKT002を呼び、返った図形のうち敷地の点が入るものを選ぶ、という三段です。マニュアルの例で式を検算すること、0件と複数件を自動で決めないこと、列を「指定値」と名乗ることの3点を守れば、メモが適用値の断定にならずに済みます。

防火・準防火地域や地区計画は、同じスクリプトでAPI名を替えれば足せます。最初は敷地数か所と1つのAPIで応答の形を確かめ、そこから対象を広げるのが安全です。

この記事を共有:XはてブLinkedIn