0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

住所文字列が「住居表示」か「地番」かを判定する方法

0
Posted at

はじめに

今回は Geolonia が公開している以下のデータ・ライブラリを利用して判定する方法を紹介します。

・normalize-japanese-addresses
https://github.com/geolonia/normalize-japanese-addresses

・japanese-addresses-v2
https://github.com/geolonia/japanese-addresses-v2

住居表示・地番の概要

日本の住所には、大きく分けて「住居表示」と「地番」があります。

  • 住居表示

    • 建物の位置を示すための住所
    • 例:○丁目○番○号
  • 地番

    • 土地を管理するための番号
    • 登記簿などで利用される
    • 例:○番地

ただし、実際の住所文字列ではどちらもハイフン表記に変換されることが多くあります。たとえば「1番2号」も「1番地2」も 1-2 のように表記されることがあります。そのため、文字列だけを見ると住居表示なのか地番なのかを判定するのは困難です。

さらに注意が必要なのは、同じ住所で、住居表示データと地番データの両方が存在するケースがあることです。つまり、ある住所文字列が「住居表示としても成立するし、地番としても成立する」場合があります。

そのため、住所が住居表示か地番かを判定するには、単純な文字列ルールではなく、元データとの照合が必要になります。

判定方法のサマリ

今回検討する方法は以下の通りです。

方法 参照データ 実装難易度 メンテナンスコスト 判定の信頼性
① normalize-japanese-addresses の metadata を使う レジストリカタログ
② japanese-addresses-v2 でビルドしてローカルデータと照合する アドレス・ベース・レジストリ ★★★ ★★★ ★★★

① normalize-japanese-addresses の metadata を使う

normalize-japanese-addresses の正規化結果には、rsdt, chiban キーがあります。rsdt を住居表示, chiban を地番として参照することで、対象住所がどちらであるかを判定できます。

メリット

実装が非常に簡単で、もっとも導入しやすい方法です。

注意点

厳密に判定する場合は注意が必要です。同じ住所に対して住居表示と地番の両方が存在する場合、結果として住居表示だけが返ることがあります。(2026年6月)

正規化に利用しているデータは japanese-addresses-v2 で作成されており、そちらのリポジトリの出典に書かれているサイト(レジストリカタログ)は終了しています。
つまり、元データを確認することができません。

まとめ

「簡易的に住居表示か地番かを知りたい」場合には向いていますが、「両方に該当する可能性まで厳密に見たい」場合には不十分です。

コード例

import { normalize } from '@geolonia/normalize-japanese-addresses';

const address = "東京都新宿区新宿一丁目1−1"

async function main(address) {
  const result = await normalize(address);
  const { rsdt, chiban } = result.metadata;

  let type;
  if (rsdt && chiban) {
    type = "住居表示・地番の両方";
  } else if (rsdt) {
    type = "住居表示";
  } else if (chiban) {
    type = "地番";
  } else {
    type = "該当なし";
  }

  console.log(type);
};

↓↓↓

住居表示

② japanese-addresses-v2 でビルドしてローカルデータと照合する

japanese-addresses-v2 ではクローン後に指定のコマンドを打つことでローカルに住所データを生成できます。この住居表示・地番データを用いて照合する方法です。

ただし、main ブランチは提供が終了しているレジストリカタログを参照しているため、データ生成に失敗します。(2026年6月)
そのため、参照先をアドレス・ベース・レジストリに修正したブランチをクローンすることで解決できます。
アドレス・ベース・レジストリ ダウンロードサイト: https://dataset.address-br.digital.go.jp/search

レジストリカタログ、アドレス・ベース・レジストリは共にデジタル庁が作成しています

メリット

参照しているデータを自分で確認できます。元データを確認できるので修正方針を立てることが容易になります。

ロジックを自分で組むため、自由で厳密な判定がしやすいです。

注意点

実装難易度とメンテナンスコストが高いです。データのダウンロード、更新、照合ロジックの実装が必要になります。

まとめ

単発の検証や簡易的な判定にはやや重い方法ですが、判定根拠を明確にしたい場合や、長期的に安定して運用したい場合には有力な選択肢です。

コード例

git clone -b 17-switch-abr-download-site git@github.com:geolonia/japanese-addresses-v2.git
cd japanese-addresses-v2
npm install
npm run run:all
import { normalize } from '@geolonia/normalize-japanese-addresses';
import fs from "fs";
import { readFile } from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";

const address = "東京都新宿区新宿一丁目1−1"

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const dir = path.join(__dirname, "path-to-japanese-addresses-v2-data");

const FIELD_MAP = {
  住居表示: ["blk_num", "rsdt_num", "rsdt_num2"],
  地番: ["prc_num1", "prc_num2", "prc_num3"],
};

const readJson = async (filePath) => {
  if (!fs.existsSync(filePath)) return null;
  return JSON.parse(await readFile(filePath, "utf-8"));
};

const parseCsvRows = (text) => {
  const lines = text.trim().split(/\r?\n/);

  // 1行目は「住居表示,町域」などなので除外する
  const [headerLine, ...dataLines] = lines.slice(1);
  const headers = headerLine.split(",");

  return dataLines.map((line) => {
    const values = line.split(",");
    return Object.fromEntries(
      headers.map((key, i) => [key, values[i] ?? ""])
    );
  });
};

const findTown = (cityData, town) => {
  return cityData.data.find((item) => {
    const itemTown = `${item.oaza_cho ?? ""}${item.chome ?? ""}`;
    return itemTown === town;
  });
};

const formatAddress = (row, rangeName) =>
  FIELD_MAP[rangeName]
    .map((field) => row[field] ?? "")
    .filter(Boolean)
    .join("-");

const findAddressRow = (rows, rangeName, addr) =>
  rows.find((row) => formatAddress(row, rangeName) === addr);

const readRangeText = async (txtPath, range) => {
  const buffer = await readFile(txtPath);

  return buffer
    .subarray(range.start, range.start + range.length)
    .toString("utf-8");
};

const main = async (address) => {
  const result = await normalize(address);

  const pref = result.pref ?? "";
  const city = result.city ?? "";
  const town = result.town ?? "";
  const addr = `${result.addr ?? ""}${result.other ?? ""}`;

  const jsonPath = path.join(dir, pref, `${city}.json`);
  const cityData = await readJson(jsonPath);

  if (!cityData) {
    console.log(`ファイルが存在しません: ${jsonPath}`);
    return;
  }

  const matchedTown = findTown(cityData, town);

  if (!matchedTown) {
    console.log(`町域が見つかりませんでした: ${town}`);
    return;
  }

  for (const [rangeName, range] of Object.entries(matchedTown.csv_ranges ?? {})) {
    const txtPath = path.join(dir, pref, `${city}-${rangeName}.txt`);

    if (!fs.existsSync(txtPath)) {
      console.log(`txtファイルが存在しません: ${txtPath}`);
      continue;
    }

    const slicedText = await readRangeText(txtPath, range);
    const rows = parseCsvRows(slicedText);
    const matchedRow = findAddressRow(rows, rangeName, addr);

    console.log(
      matchedRow
        ? `${rangeName}: ${formatAddress(matchedRow, rangeName)}`
        : `${rangeName}: 一致なし`
    );
  }
};

main(address);

↓↓↓

地番: 1-1
住居表示: 1-1

japanese-addresses-v2 の API について

japanese-addresses-v2 に API はありますが、公開の停止や変更など行う可能性があり、公式も自身でデータを作成することを推奨しています。

おわりに

今回は normalize-japanese-addressesjapanese-addresses-v2 を用いた住居表示・地番の判定方法を紹介しました。

どちらも多くの住所では同じ判定結果になりますが、一部では結果が異なったり、片方でしか判定できなかったりする場合があります。

そのため、どちらか一方が優れているというわけではなく、それぞれに特徴があります。用途に応じて使い分ける、あるいは両方を組み合わせて利用するとよいでしょう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?