砂防基盤図の古い航空写真を刷新

砂防基盤図の古い航空写真を刷新:QGISで地理院シームレス写真を1/1,250図郭ごとに0.25 m/pxで一括生成し、土石流区域設定システムへ取り込む

砂防基盤図の古い航空写真を刷新:QGISで地理院シームレス写真を1/1,250図郭ごとに0.25 m/pxで一括生成し、土石流区域設定システムへ取り込む

目的

既存の砂防基盤図に付属する航空写真が古いため、国土地理院「シームレス写真」を用いて最新の画像を国土基本図郭(1/1,250)ごとに作成し、砂防フロンティア「土石流区域設定システム」へ取り込むための素材(JPG+JGW または GeoTIFF+TFW)を一括出力します。

成果物

  • 各図郭ごとに ort_MAPID.jpg(または .tif)と JGW/TFW を生成
  • 解像度 0.25 m/px 固定:1/1,250 図郭(約 1000×750 m)は4000×3000 pxで統一
  • 出力先は図郭シェープ(.shp)と同じフォルダ

前提

  • QGIS 3.x
  • 図郭ポリゴン(例:ZUKAKU_1250_7CRS はメートル系(平面直角)
  • 地理院シームレス写真(XYZ)URL:https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg

手順(概要)

  1. 図郭シェープをQGISへ追加(必要なら対象図郭のみ選択)。
  2. 下記の PyQGIS スクリプトを「Python コンソール → エディタ」に貼り付けて F5 実行。
  3. 図郭 .shp と同じフォルダに ort_*.jpg.jgw(または .tif.tfw)が出力されます。

PyQGIS スクリプト(0.25 m/px 固定)

# 1/1,250 図郭に合わせて 0.25 m/px で切り出し(JPG + JGW)
# 1/2,500 図郭も同じ m/px で書き出し可能(8000×6000 px)

import os
from qgis.core import (
    QgsProject, QgsRasterLayer, QgsWkbTypes, QgsUnitTypes,
    QgsMapSettings, QgsMapRendererParallelJob
)
from qgis.PyQt.QtCore import QSize

# ===== 設定 =====
POLY_NAME   = 'ZUKAKU_1250_7'                 # 図郭レイヤ名
NAME_FIELD  = 'MAPID'                          # 出力名に使う属性(例:HD4431)
PIXEL_M     = 0.25                             # ★ 0.25 m/px 固定
XYZ_NAME    = 'GSI_seamless'                   # 既に追加済みならこの名前で取得
XYZ_URI     = ('type=xyz&'
               'url=https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg&'
               'zmax=18&zmin=14')
OUTPUT_FMT  = 'JPG'                            # 'JPG' または 'TIFF'
JPEG_QUALITY= 95                               # JPG の画質
SET_DPI     = 254                              # 印刷用のDPIメタ(任意・見え方は不変)
# ===============

poly = QgsProject.instance().mapLayersByName(POLY_NAME)[0]
if poly.crs().mapUnits() != QgsUnitTypes.DistanceMeters:
    raise ValueError('図郭レイヤのCRSはメートル系(平面直角など)にしてください')

out_dir = os.path.dirname(poly.source().split('|')[0])
print(f'出力フォルダ: {out_dir}')

lst = QgsProject.instance().mapLayersByName(XYZ_NAME)
xyz = lst[0] if lst else QgsRasterLayer(XYZ_URI, XYZ_NAME, 'wms')
if not lst:
    if not xyz.isValid(): raise ValueError('シームレス写真レイヤ作成に失敗')
    QgsProject.instance().addMapLayer(xyz, False)

feats = poly.selectedFeatures() or list(poly.getFeatures())
print(f'対象: {len(feats)} 件  |  解像度: {PIXEL_M:.2f} m/px')

for f in feats:
    code = str(f[NAME_FIELD] or f.id()).strip()
    ext  = f.geometry().boundingBox()
    w = int(round(ext.width()  / PIXEL_M))
    h = int(round(ext.height() / PIXEL_M))

    ms = QgsMapSettings()
    ms.setLayers([xyz])
    ms.setDestinationCrs(poly.crs())
    ms.setExtent(ext)
    ms.setOutputSize(QSize(w, h))

    job = QgsMapRendererParallelJob(ms)
    job.start(); job.waitForFinished()
    img = job.renderedImage()

    if SET_DPI:
        dpm = int(SET_DPI / 0.0254)
        img.setDotsPerMeterX(dpm); img.setDotsPerMeterY(dpm)

    stem = f'ort_{code}'
    if OUTPUT_FMT.upper() == 'TIFF':
        out_path = os.path.join(out_dir, stem + '.tif')
        ok = img.save(out_path, 'TIFF')
        wld = os.path.join(out_dir, stem + '.tfw')
    else:
        out_path = os.path.join(out_dir, stem + '.jpg')
        ok = img.save(out_path, 'JPG', JPEG_QUALITY)
        wld = os.path.join(out_dir, stem + '.jgw')

    xres = PIXEL_M; yres = -PIXEL_M
    xul  = ext.xMinimum() + xres/2.0
    yul  = ext.yMaximum() - PIXEL_M/2.0
    with open(wld, 'w', encoding='ascii') as fp:
        fp.write(f'{xres:.10f}\n0.0\n0.0\n{yres:.10f}\n{xul:.10f}\n{yul:.10f}\n')

    print(f'✓ {code} → {os.path.basename(out_path)} [{w}×{h}px, {PIXEL_M:.2f} m/px]')

print('完了')
取り込み(砂防フロンティア・土石流区域設定システム)に向けたポイント
・システム側が受け付けるラスタは多くの場合「JPG+JGW」または「GeoTIFF(+TFW)」。本記事の出力は両対応です。
・CRS は作業系(例:平面直角 第○系)で統一してください。システム側要求と合わせるのが無難です。
・図郭ごとにファイル名へ MAPID を付けておくと、位置の突合や登録時の検索が容易になります。
・既存の砂防基盤図に重ねて目視確認し、欠落や雲の多いタイルがあれば該当図郭だけ再出力します。

運用ヒント

  • より精細に:PIXEL_M=0.20(PC負荷に注意)。逆に軽く:0.30–0.40
  • 一部図郭のみ更新:QGISで対象図郭を選択して実行すると、選択分だけ出力
  • 画像メタの DPI は印刷用。地図の精細さは JGW/TFW1行目(m/px)で決まります。

ライセンス表記

出典:地理院タイル(シームレス写真)。利用規約に従い、出力物・画面等に出典を明記してください。

コメント

  1. 下記は、出力TIFバージョン


    # 1/1,250 図郭に合わせて 0.25 m/px で切り出し(TIFF + TFW)
    # 1/2,500 図郭も同じ m/px で書き出し可能(8000×6000 px)

    import os
    from qgis.core import (
    QgsProject, QgsRasterLayer, QgsWkbTypes, QgsUnitTypes,
    QgsMapSettings, QgsMapRendererParallelJob
    )
    from qgis.PyQt.QtCore import QSize

    # ===== 設定 =====
    POLY_NAME = 'ZUKAKU_1250_7' # 図郭レイヤ名
    NAME_FIELD = 'MAPID' # 出力名に使う属性(例:HD4431)
    PIXEL_M = 0.25 # ★ 0.25 m/px 固定
    XYZ_NAME = 'GSI_seamless' # 既に追加済みならこの名前で取得
    XYZ_URI = ('type=xyz&'
    'url=https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg&'
    'zmax=18&zmin=14')
    SET_DPI = 254 # 印刷用のDPIメタ(任意・見え方は不変)
    # ===============

    poly = QgsProject.instance().mapLayersByName(POLY_NAME)[0]
    if poly.crs().mapUnits() != QgsUnitTypes.DistanceMeters:
    raise ValueError('図郭レイヤのCRSはメートル系(平面直角など)にしてください')

    out_dir = os.path.dirname(poly.source().split('|')[0])
    print(f'出力フォルダ: {out_dir}')

    lst = QgsProject.instance().mapLayersByName(XYZ_NAME)
    xyz = lst[0] if lst else QgsRasterLayer(XYZ_URI, XYZ_NAME, 'wms')
    if not lst:
    if not xyz.isValid(): raise ValueError('シームレス写真レイヤ作成に失敗')
    QgsProject.instance().addMapLayer(xyz, False)

    feats = poly.selectedFeatures() or list(poly.getFeatures())
    print(f'対象: {len(feats)} 件 | 解像度: {PIXEL_M:.2f} m/px')

    for f in feats:
    code = str(f[NAME_FIELD] or f.id()).strip()
    ext = f.geometry().boundingBox()
    w = int(round(ext.width() / PIXEL_M))
    h = int(round(ext.height() / PIXEL_M))

    ms = QgsMapSettings()
    ms.setLayers([xyz])
    ms.setDestinationCrs(poly.crs())
    ms.setExtent(ext)
    ms.setOutputSize(QSize(w, h))

    job = QgsMapRendererParallelJob(ms)
    job.start(); job.waitForFinished()
    img = job.renderedImage()

    if SET_DPI:
    dpm = int(SET_DPI / 0.0254)
    img.setDotsPerMeterX(dpm); img.setDotsPerMeterY(dpm)

    stem = f'ort_{code}'
    out_path = os.path.join(out_dir, stem + '.tif')
    ok = img.save(out_path, 'TIFF')
    if not ok:
    raise IOError(f'TIFF保存に失敗: {out_path}')

    # ワールドファイル(TFW)
    wld = os.path.join(out_dir, stem + '.tfw')
    xres = PIXEL_M; yres = -PIXEL_M
    xul = ext.xMinimum() + xres/2.0
    yul = ext.yMaximum() - PIXEL_M/2.0
    with open(wld, 'w', encoding='ascii') as fp:
    fp.write(f'{xres:.10f}\n0.0\n0.0\n{yres:.10f}\n{xul:.10f}\n{yul:.10f}\n')

    print(f'✓ {code} → {os.path.basename(out_path)} [{w}×{h}px, {PIXEL_M:.2f} m/px]')

    print('完了')

    返信削除
  2. QGISで、出力したい図郭レイヤ(toyama_lvl2500_epsg6675 など)をアクティブにする(レイヤパネルでクリックして青選択)。

    そのレイヤ上で必要な図郭だけ選択。

    下記コードを Python コンソールで実行。

    返信削除
  3. import os
    from qgis.core import (
    QgsProject,
    QgsRasterLayer,
    QgsUnitTypes,
    QgsMapSettings,
    QgsMapRendererParallelJob,
    )
    from qgis.PyQt.QtCore import QSize

    # ===== 設定 =====
    NAME_FIELD = 'code2500' # ファイル名に使う図郭コード列
    PIXEL_M = 0.25
    XYZ_NAME = 'GSI_seamless'
    XYZ_URI = (
    'type=xyz&'
    'url=https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg&'
    'zmax=18&zmin=14'
    )
    SET_DPI = 254
    # ===============

    # アクティブな図郭レイヤを取得
    poly = iface.activeLayer()
    if not poly:
    raise ValueError('図郭レイヤをアクティブにしてから実行してください')

    print(f'使用レイヤ: {poly.name()}')

    # 座標単位チェック
    if poly.crs().mapUnits() != QgsUnitTypes.DistanceMeters:
    raise ValueError('図郭レイヤのCRSは平面直角などメートル系である必要があります')

    # 出力フォルダ = 図郭レイヤのソースと同じ場所
    src_path = poly.source().split('|')[0]
    out_dir = os.path.dirname(src_path)
    if not out_dir:
    out_dir = os.path.expanduser('~')
    print(f'出力フォルダ: {out_dir}')

    # GSIシームレスレイヤ取得
    xyz_layers = QgsProject.instance().mapLayersByName(XYZ_NAME)
    if xyz_layers:
    xyz = xyz_layers[0]
    else:
    xyz = QgsRasterLayer(XYZ_URI, XYZ_NAME, 'wms')
    if not xyz.isValid():
    raise ValueError('GSIシームレス写真レイヤの作成に失敗しました')
    QgsProject.instance().addMapLayer(xyz, False)

    # 対象図郭 = 選択されていれば選択のみ / なければ全件
    selected = poly.selectedFeatures()
    if selected:
    feats = selected
    print(f'選択図郭のみ出力します: {len(feats)} 件')
    else:
    feats = list(poly.getFeatures())
    print(f'※選択なしのため全図郭 {len(feats)} 件を対象とします')

    print(f'解像度: {PIXEL_M:.2f} m/px')

    for f in feats:
    code = str(f[NAME_FIELD] or f.id()).strip()
    if not code:
    continue

    ext = f.geometry().boundingBox()
    w = int(round(ext.width() / PIXEL_M))
    h = int(round(ext.height() / PIXEL_M))

    ms = QgsMapSettings()
    ms.setLayers([xyz])
    ms.setDestinationCrs(poly.crs())
    ms.setExtent(ext)
    ms.setOutputSize(QSize(w, h))

    job = QgsMapRendererParallelJob(ms)
    job.start()
    job.waitForFinished()
    img = job.renderedImage()

    if SET_DPI:
    dpm = int(SET_DPI / 0.0254)
    img.setDotsPerMeterX(dpm)
    img.setDotsPerMeterY(dpm)

    stem = f'ort_{code}'
    out_path = os.path.join(out_dir, stem + '.tif')
    if not img.save(out_path, 'TIFF'):
    raise IOError(f'TIFF保存に失敗: {out_path}')

    # ワールドファイル(TFW)
    wld = os.path.join(out_dir, stem + '.tfw')
    xres = PIXEL_M
    yres = -PIXEL_M
    xul = ext.xMinimum() + xres / 2.0
    yul = ext.yMaximum() - PIXEL_M / 2.0
    with open(wld, 'w', encoding='ascii') as fp:
    fp.write(f'{xres:.10f}\n')
    fp.write('0.0\n')
    fp.write('0.0\n')
    fp.write(f'{yres:.10f}\n')
    fp.write(f'{xul:.10f}\n')
    fp.write(f'{yul:.10f}\n')

    print(f'✓ {code} → {os.path.basename(out_path)} [{w}×{h}px]')

    print('完了')

    返信削除
  4. import os
    from qgis.core import (
    QgsProject, QgsRasterLayer, QgsUnitTypes,
    QgsMapSettings, QgsMapRendererParallelJob
    )
    from qgis.PyQt.QtCore import QSize

    # ===== 設定 =====
    NAME_FIELD = 'code2500' # 図郭コード列名(例: GE881 等)
    PIXEL_M = 0.25 # m/px
    XYZ_NAME = 'GSI_seamless'
    XYZ_URI = (
    'type=xyz&'
    'url=https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg&'
    'zmax=18&zmin=14&'
    'crs=EPSG:3857' # ★ 明示
    )
    SET_DPI = 254
    # ===============

    # --- 図郭レイヤ取得 ---
    poly = iface.activeLayer()
    if not poly:
    raise ValueError('図郭レイヤをアクティブにしてから実行してください')
    print(f'使用レイヤ: {poly.name()}')

    # --- CRS 単位チェック(m 必須)---
    if poly.crs().mapUnits() != QgsUnitTypes.DistanceMeters:
    raise ValueError('図郭レイヤのCRSはメートル系(平面直角など)が必要です')

    # --- 出力フォルダ決定 ---
    src_path = poly.source().split('|')[0]
    out_dir = os.path.dirname(src_path) if src_path else ''
    if not out_dir:
    out_dir = os.path.expanduser('~')
    if not os.path.isdir(out_dir) or not os.access(out_dir, os.W_OK):
    raise IOError(f'出力フォルダに書き込みできません: {out_dir}')
    print(f'出力フォルダ: {out_dir}')

    # --- XYZ(GSIシームレス) ---
    xyz_layers = QgsProject.instance().mapLayersByName(XYZ_NAME)
    if xyz_layers:
    xyz = xyz_layers[0]
    else:
    xyz = QgsRasterLayer(XYZ_URI, XYZ_NAME, 'wms') # provider=wms で OK(type=xyz)
    if not xyz.isValid():
    raise ValueError('GSIシームレス写真レイヤの作成に失敗しました(ネットワーク/URIを確認)')
    QgsProject.instance().addMapLayer(xyz, False)

    # --- 対象図郭一覧 ---
    selected = poly.selectedFeatures()
    feats = selected if selected else list(poly.getFeatures())
    print(('選択図郭のみ' if selected else '全図郭') + f'を出力します: {len(feats)} 件')
    print(f'解像度: {PIXEL_M:.2f} m/px')

    # --- ループ処理 ---
    for f in feats:
    # 図郭コード
    try:
    code_val = f[NAME_FIELD]
    except Exception:
    raise KeyError(f'属性「{NAME_FIELD}」が見つかりません。列名を確認してください。')

    code = str(code_val if code_val not in (None, '') else f.id()).strip()
    if not code:
    print(' → 属性が空のためスキップ')
    continue

    # 図郭範囲
    ext = f.geometry().boundingBox()
    # まれに幅/高さ 0 になるのを防ぐ
    if ext.width() == 0 or ext.height() == 0:
    ext.grow(0.01) # 1cm バッファ
    w = int(round(ext.width() / PIXEL_M))
    h = int(round(ext.height() / PIXEL_M))
    if w <= 0 or h <= 0:
    raise ValueError(f'ピクセルサイズが0または負です (code={code}, w={w}, h={h}, ext={ext.toString()})')

    # レンダリング設定
    ms = QgsMapSettings()
    ms.setLayers([xyz])
    ms.setDestinationCrs(poly.crs()) # 図郭CRSへ再投影して出力
    ms.setExtent(ext)
    ms.setOutputSize(QSize(w, h))

    # レンダリング
    job = QgsMapRendererParallelJob(ms)
    job.start(); job.waitForFinished()
    img = job.renderedImage()

    # DPI(任意)
    if SET_DPI:
    dpm = int(SET_DPI / 0.0254)
    img.setDotsPerMeterX(dpm)
    img.setDotsPerMeterY(dpm)

    # 保存
    stem = f'ort_{code}'
    out_path = os.path.join(out_dir, stem + '.tif')
    ok = img.save(out_path, 'TIFF')
    if not ok:
    raise IOError(f'TIFF保存に失敗: {out_path}')

    # ワールドファイル(TFW)
    wld = os.path.join(out_dir, stem + '.tfw')
    xres = PIXEL_M
    yres = -PIXEL_M
    xul = ext.xMinimum() + xres / 2.0
    yul = ext.yMaximum() - PIXEL_M / 2.0
    with open(wld, 'w', encoding='ascii') as fp:
    fp.write(f'{xres:.10f}\n0.0\n0.0\n{yres:.10f}\n{xul:.10f}\n{yul:.10f}\n')

    print(f'✓ {code} → {os.path.basename(out_path)} [{w}×{h}px]')

    print('完了')

    返信削除

コメントを投稿

このブログの人気の投稿

石川県:土砂災害(特別)警戒区域+CS立体図

日本でよく使う EPSG コード 一覧

設計定数を求めるための代表N値について