🗾

GIS境界データを使って分析(1. データ収集)

に公開

最近日本の地方自治体のデータの収集して加工したり、地図表示するような作業が増えてきて、時間がたつと忘れてしまうので、記事にして書いておこうと思いました。
だいたい、以下の内容を自分の忘備録のためにも書くつもりです。
動作を確認して作成している日は2025年08月10日です、時間がたてば今回の記事の対象としているサイトの構成の変更などでこの内容がそのまま使えなくなる可能性はあります。
境界データの収集(マニュアルでダウンロードして確認とAPIで自動データダウンロード)
境界データの加工
境界データのデータベースへに以降POLYGON型に対応したデータベースを試す

目的によってどこからデータを取ってくるのか変わりますが、まず日本国内の話であり、無償でかつ自治体管轄ベースの区切りで境界データが欲しければ、総務省が運営している政府統計窓口e-statがおすすめです。
https://www.e-stat.go.jp/
今回は兵庫県の市川町(いちかわちょう)、福崎町(ふくさきちょう)、神河町(かみかわちょう)
3つの自治体から構成された兵庫県の神崎郡(かんざきぐん)のデータを取ってきます。

マニュアル操作でダウンロード

*まずはe-statのWEBページからそのままデータを取る方法です
e-statのサイトにアクセスすると以下のような画面が現れます。

ずーとスクロールダウンして境界データダウンロードを押します。

小地域を選びます

いろいろな調査のデータがあるようですが今回は自治体の境界だけ知りたいかったので国勢調査を選びました。のちのちその他の国勢調査統計データとも紐づけられる可能性もあるので無難にそれを選びます。

一応年度は一番新しい2020年の小地域(基本単位区)を選びます。

次にファイルの携帯ですがSHAPEFILEの世界測地系緯度経度を選びます。

次に、都道府県、と市町村を選んでいきます。(今回は兵庫県から市川町,福崎町,神河町の3町)


データをクリックするとデータがダウンロードされます。
一旦少しスクショを張って簡単に説明入れただけの駆け足記事にはなってしまいました。

自動でできるのか?

ちなみにこのあたり自動化できないかとチェックしましたがe-statはAPIはあるが対象は統計データ中心で、境界データをAPI経由で自動でデータをのはサポートしてなさそうです。しかし、公式ではないですがダウンロード自体は認証が不要なのでダウンロードして解凍するようなことはできそうです。でも公式ではないのでこれでサービスなどを提供した場合はサイトの構成などが変わったりしたときに自動化プロセスは壊れてしまうかもしれません。(いろいろ細かいダウンロードが必要になったときに、個人的にためしてマウスを動かす手を多少休めるために試すならいいかもしれません。)
上記3つの町のSHAPEファイルのURLは以下のようです。

https://www.e-stat.go.jp/gis/statmap-search/data?dlserveyId=B002005212020&code=28442&coordSys=1&format=shape&downloadType=5&datum=2000
https://www.e-stat.go.jp/gis/statmap-search/data?dlserveyId=B002005212020&code=28443&coordSys=1&format=shape&downloadType=5&datum=2000
https://www.e-stat.go.jp/gis/statmap-search/data?dlserveyId=B002005212020&code=28446&coordSys=1&format=shape&downloadType=5&datum=2000

調査年、調査のタイプ、フォーマットなどでパラメータがきめられているようで、これに基づいてリダイレクトでダウンロードができるのではないかと推測します。違うのはcode=[自治体のID]
ここでは福崎=28442,市川=28443,神河=28446です。この自治体のID一覧自体は
https://www.e-stat.go.jp/municipalities/cities/areacodesearch?utm_source=chatgpt.com
から市区町村を探すに行き、

必要なものチェック入れてダウンロードできます。

下にスクロールするとダウンロードボタンがあります。

さて、実際にPYTHONのコードでSHAPEファイルで境界データを取る方法です。まず必要なライブラリをインストールrequestsというライブラリだけです。

# pip install requests

そして以下のようなコードを実行します。(ここではdownload0.pyと命名)

import os
import re
import time
import zipfile
from pathlib import Path
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

BASE_URL = (
    "https://www.e-stat.go.jp/gis/statmap-search/data"
    "?dlserveyId=B002005212020&coordSys=1&format=shape&downloadType=5&datum=2000&code={code}"
)

# ダウンロードの安定化(リトライ)設定
def build_session() -> requests.Session:
    s = requests.Session()
    s.headers.update({
        "User-Agent": "Mozilla/5.0 (compatible; eStatDownloader/1.0)",
        "Referer": "https://www.e-stat.go.jp/gis/statmap-search",  # あると安定
        "Accept": "*/*",
    })
    retry = Retry(
        total=5,
        connect=5,
        read=5,
        backoff_factor=0.8,
        status_forcelist=(429, 500, 502, 503, 504),
        allowed_methods=["GET", "HEAD"],
    )
    s.mount("https://", HTTPAdapter(max_retries=retry))
    s.max_redirects = 10
    return s

def infer_filename(resp: requests.Response, fallback: str) -> str:
    cd = resp.headers.get("Content-Disposition", "")
    m = re.search(r'filename\*?=(?:UTF-8\'\')?"?([^"]+)"?', cd)
    if m:
        return m.group(1)
    return fallback

def download_one(session: requests.Session, code: str, outdir: Path) -> Path:
    url = BASE_URL.format(code=code)
    with session.get(url, stream=True, allow_redirects=True, timeout=60) as r:
        r.raise_for_status()
        # ファイル名推定(なければ code.zip)
        fname = infer_filename(r, f"{code}.zip")
        # もし日本語ファイル名を含む場合の安全策
        try:
            fname = requests.utils.unquote(fname)
        except Exception:
            pass
        outpath = outdir / fname
        with open(outpath, "wb") as f:
            for chunk in r.iter_content(chunk_size=1024 * 1024):
                if chunk:
                    f.write(chunk)
        return outpath

def extract_zip(zippath: Path, outdir: Path) -> Path:
    # Shapefileは複数拡張子を伴うのでZIPをまとめて解凍
    with zipfile.ZipFile(zippath) as z:
        z.extractall(outdir)
    return outdir

def download_estat_codes(codes, out_root="/tmp/e_stat_kanzaki"):
    out_root = Path(out_root)
    out_root.mkdir(parents=True, exist_ok=True)

    session = build_session()
    saved = []
    for code in codes:
        try:
            print(f"[INFO] downloading code={code} ...")
            zip_path = download_one(session, str(code), out_root)
            print(f"[OK]  saved: {zip_path}")
            # 解凍先は code サブフォルダに
            extract_dir = out_root / f"{code}"
            extract_dir.mkdir(exist_ok=True)
            extract_zip(zip_path, extract_dir)
            print(f"[OK]  extracted to: {extract_dir}")
            saved.append((code, zip_path, extract_dir))
            time.sleep(0.5)  # サーバ負荷に配慮して少し待つ
        except Exception as e:
            print(f"[ERR] code={code}: {e}")
    return saved

if __name__ == "__main__":
    # 神崎郡(兵庫)の市町村コード例
    codes = ["28442", "28443", "28446"]  # 福崎町, 市川町, 神河町
    results = download_estat_codes(codes, "/tmp/e_stat_kanzaki")
    print("\n== DONE ==")
    for code, zip_path, extract_dir in results:
        print(code, "->", zip_path, "->", extract_dir)

これを実装すると、所定のフォルダーにデータがダウンロードされます!

# python download0.py
[INFO] downloading code=28442 ...
[OK]  saved: /tmp/e_stat_kanzaki/B002005212020DDSWC28442.zip
[OK]  extracted to: /tmp/e_stat_kanzaki/28442
[INFO] downloading code=28443 ...
[OK]  saved: /tmp/e_stat_kanzaki/B002005212020DDSWC28443.zip
[OK]  extracted to: /tmp/e_stat_kanzaki/28443
[INFO] downloading code=28446 ...
[OK]  saved: /tmp/e_stat_kanzaki/B002005212020DDSWC28446.zip
[OK]  extracted to: /tmp/e_stat_kanzaki/28446

== DONE ==
28442 -> /tmp/e_stat_kanzaki/B002005212020DDSWC28442.zip -> /tmp/e_stat_kanzaki/28442
28443 -> /tmp/e_stat_kanzaki/B002005212020DDSWC28443.zip -> /tmp/e_stat_kanzaki/28443
28446 -> /tmp/e_stat_kanzaki/B002005212020DDSWC28446.zip -> /tmp/e_stat_kanzaki/28446
(gistest) root@ip-172-31-2-12:/opt/gistest# ls /tmp/e_stat_kanzaki/28442
r2kb28442.dbf  r2kb28442.prj  r2kb28442.shp  r2kb28442.shx

次回は、COLAB上でこの収集したデータのちょっとした加工、と表示(ただく結合するだけとデータ確認)といろいろな地図表示を説明した記事です。
https://zenn.dev/takeofuture/articles/f3bc63e1083860

Discussion