土石流区域設定支援システムと同等な横断測線、流下方向(縦断測線)をQGISで再現

はじめに

土石流区域設定支援システム(dem.mdb)のCSVデータより、横断測線、縦断測線をQGIS上で可視化・活用するためのPythonスクリプトを作成しました。

システム独自の「カンマ区切り座標」や「mm単位」を自動変換し、以下の2つのレイヤを自動生成します。

  1. 流下方向(縦断):勾配に応じて色分けし、始点・終点の座標を持たせたライン
  2. 横断測線:各測線の始点と終点を結び、連番を振ったライン 

1. 流下方向(縦断)の作成ツール

縦断データのCSVを読み込み、以下の処理を行います。

  • mm単位の座標をm単位に変換してラインを作成
  • 勾配(角度)を計算し、自動で色分け(0~2度、2~3度...30度以上)
  • 属性テーブルに「勾配角度」と「始点・終点座標(X,Y)」を出力

スクリプトコード

import csv
import math
from qgis.core import (
    QgsProject,
    QgsVectorLayer,
    QgsFeature,
    QgsGeometry,
    QgsField,
    QgsPointXY,
    QgsGraduatedSymbolRenderer,
    QgsRendererRange,
    QgsSymbol,
    QgsCoordinateReferenceSystem
)
from qgis.utils import iface
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PyQt5.QtCore import QVariant
from PyQt5.QtGui import QColor

def main_process():
    # 1. ファイル選択
    file_path, _ = QFileDialog.getOpenFileName(
        None, "CSVファイルを選択してください", "", "CSV Files (*.csv);;All Files (*)"
    )
    if not file_path: return

    # 2. データを読み込み
    points = []
    encodings = ['cp932', 'utf-8']
    data_rows = []
    
    for enc in encodings:
        try:
            with open(file_path, 'r', encoding=enc, newline='') as f:
                reader = csv.reader(f)
                data_rows = list(reader)
            break
        except UnicodeDecodeError: continue
            
    if not data_rows: return

    # 座標データ列探索
    target_col_index = -1
    start_row = 1 if len(data_rows) > 1 else 0
    for i in range(start_row, min(len(data_rows), 5)):
        row = data_rows[i]
        for idx, val in enumerate(row):
            if val.count(',') >= 3: 
                target_col_index = idx
                break
        if target_col_index != -1: break
    
    if target_col_index == -1: return

    # データ解析
    for i in range(start_row, len(data_rows)):
        row = data_rows[i]
        if len(row) <= target_col_index: continue
        val_str = row[target_col_index]
        try:
            parts = val_str.split(',')
            if len(parts) < 4: continue
            # [1]=Z, [2]=X, [3]=Y (mm->m)
            z = float(parts[1]) / 1000.0
            x = float(parts[2]) / 1000.0
            y = float(parts[3]) / 1000.0
            points.append({'x': x, 'y': y, 'z': z})
        except: continue

    if len(points) < 2: return

    # 3. レイヤ作成(流下方向)
    crs = QgsProject.instance().crs().authid()
    if not crs: crs = "EPSG:6668"
    line_layer = QgsVectorLayer(f"LineString?crs={crs}", "流下方向", "memory")
    pr = line_layer.dataProvider()
    
    # 属性追加
    pr.addAttributes([
        QgsField("slope_angle", QVariant.Double),
        QgsField("Start_X", QVariant.Double),
        QgsField("Start_Y", QVariant.Double),
        QgsField("End_X", QVariant.Double),
        QgsField("End_Y", QVariant.Double)
    ])
    line_layer.updateFields()

    new_features = []
    
    # 4. ライン生成と勾配計算
    for i in range(len(points) - 1):
        p1 = points[i]
        p2 = points[i+1]
        dx = p2['x'] - p1['x']
        dy = p2['y'] - p1['y']
        dz = p2['z'] - p1['z']
        dist = math.sqrt(dx*dx + dy*dy)
        angle = 0.0
        if dist != 0:
            angle = math.degrees(math.atan2(dz, dist))
        elif dz != 0:
            angle = 90.0 if dz > 0 else -90.0
            
        geom = QgsGeometry.fromPolylineXY([QgsPointXY(p1['x'], p1['y']), QgsPointXY(p2['x'], p2['y'])])
        f = QgsFeature()
        f.setGeometry(geom)
        f.setAttributes([angle, p1['x'], p1['y'], p2['x'], p2['y']])
        new_features.append(f)

    pr.addFeatures(new_features)
    line_layer.updateExtents()
    
    # 5. 色分け適用
    target_field = 'abs("slope_angle")'
    categories = [
        (0.0,  2.0,  QColor(0, 255, 255), "0度 ~ 2度未満"),
        (2.0,  3.0,  QColor(0, 0, 255),   "2度 ~ 3度未満"),
        (3.0,  7.0,  QColor(0, 255, 0),   "3度 ~ 7度未満"),
        (7.0,  10.0, QColor(139, 0, 0),   "7度 ~ 10度未満"),
        (10.0, 15.0, QColor(255, 0, 255), "10度 ~ 15度未満"),
        (15.0, 20.0, QColor(205, 133, 63), "15度 ~ 20度未満"),
        (20.0, 30.0, QColor(255, 165, 0),  "20度 ~ 30度未満"),
        (30.0, 90.0, QColor(255, 0, 0),    "30度以上")
    ]
    ranges = []
    for lower, upper, color, label in categories:
        symbol = QgsSymbol.defaultSymbol(line_layer.geometryType())
        symbol.setColor(color)
        symbol.setWidth(0.8)
        ranges.append(QgsRendererRange(lower, upper, symbol, label))

    renderer = QgsGraduatedSymbolRenderer(target_field, ranges)
    renderer.setMode(QgsGraduatedSymbolRenderer.Custom)
    line_layer.setRenderer(renderer)

    QgsProject.instance().addMapLayer(line_layer)
    QMessageBox.information(None, "完了", "CSVから色分けラインを作成しました。\n属性テーブルに座標(m)が入っています。")

main_process()

勾配ごとに色分けされた流下方向ライン


2. 横断測線の作成ツール

横断測線のCSVデータから、各測線(IDグループ:10000, 20000...)ごとの始点と終点を抽出し、ラインを作成します。

  • IDごとにグループ化し、最初と最後を自動抽出
  • ラインを作成し、属性に連番(Seq_No)を付与
  • 始点・終点のX,Y座標(m単位)を属性テーブルに格納

スクリプトコード

import csv
from qgis.core import (
    QgsProject,
    QgsVectorLayer,
    QgsFeature,
    QgsGeometry,
    QgsField,
    QgsPointXY
)
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PyQt5.QtCore import QVariant

def create_transverse_lines_final():
    # 1. ファイル選択
    file_path, _ = QFileDialog.getOpenFileName(
        None, "CSVファイルを選択してください", "", "CSV Files (*.csv);;All Files (*)"
    )
    if not file_path: return

    # 2. データ読み込み
    groups = {}
    encodings = ['cp932', 'utf-8']
    rows = []
    
    loaded = False
    for enc in encodings:
        try:
            with open(file_path, 'r', encoding=enc, newline='') as f:
                reader = csv.DictReader(f)
                if not reader.fieldnames: continue
                field_map = {name.strip(): name for name in reader.fieldnames}
                id_col = next((k for k in field_map if 'ID' in k), None)
                il_col = next((k for k in field_map if 'ILData' in k), None)
                if not id_col or not il_col: continue
                for row in reader:
                    rows.append({'id_col': id_col, 'il_col': il_col, 'data': row})
                loaded = True
            break
        except UnicodeDecodeError: continue
            
    if not loaded: return

    # 3. グループごとの始点・終点抽出
    for item in rows:
        row = item['data']
        id_key = item['id_col']
        try:
            current_id_val = int(row[id_key])
            group_key = current_id_val // 10000
            if group_key == 0: continue 

            if group_key not in groups:
                groups[group_key] = {
                    'min_id': current_id_val, 'min_row': row,
                    'max_id': current_id_val, 'max_row': row
                }
            else:
                if current_id_val < groups[group_key]['min_id']:
                    groups[group_key]['min_id'] = current_id_val
                    groups[group_key]['min_row'] = row
                if current_id_val > groups[group_key]['max_id']:
                    groups[group_key]['max_id'] = current_id_val
                    groups[group_key]['max_row'] = row
        except ValueError: continue

    # 4. レイヤ作成(横断測線)
    crs = QgsProject.instance().crs().authid()
    if not crs: crs = "EPSG:6668"
    vl = QgsVectorLayer(f"LineString?crs={crs}", "横断測線", "memory")
    pr = vl.dataProvider()
    
    # 属性定義
    pr.addAttributes([
        QgsField("Seq_No", QVariant.Int),
        QgsField("Group_ID", QVariant.Int),
        QgsField("Start_ID", QVariant.Int),
        QgsField("End_ID", QVariant.Int),
        QgsField("Start_X", QVariant.Double),
        QgsField("Start_Y", QVariant.Double),
        QgsField("End_X", QVariant.Double),
        QgsField("End_Y", QVariant.Double)
    ])
    vl.updateFields()
    
    new_features = []
    il_col_name = rows[0]['il_col']
    sorted_group_keys = sorted(groups.keys())

    # 5. ライン作成
    for seq_index, g_key in enumerate(sorted_group_keys):
        g_val = groups[g_key]
        
        def get_xy(row_data):
            try:
                val_str = row_data[il_col_name]
                parts = val_str.split(',')
                if len(parts) < 4: return None
                # [2]=X, [3]=Y, mm->m
                x = float(parts[2]) / 1000.0
                y = float(parts[3]) / 1000.0
                return QgsPointXY(x, y)
            except: return None

        pt_start = get_xy(g_val['min_row'])
        pt_end = get_xy(g_val['max_row'])
        
        if pt_start and pt_end:
            line_geom = QgsGeometry.fromPolylineXY([pt_start, pt_end])
            feat = QgsFeature()
            feat.setGeometry(line_geom)
            feat.setAttributes([
                seq_index, g_key * 10000, g_val['min_id'], g_val['max_id'],
                pt_start.x(), pt_start.y(), pt_end.x(), pt_end.y()
            ])
            new_features.append(feat)

    pr.addFeatures(new_features)
    vl.updateExtents()
    QgsProject.instance().addMapLayer(vl)
    QMessageBox.information(None, "完了", "レイヤ「横断測線」を作成しました。\n属性テーブルを確認してください。")

create_transverse_lines_final()

自動作成された横断測線ライン

おわりに

これらのスクリプトにより、複雑な手順を踏まずに「CSVを選択するだけ」で、土石流支援システムと同等な形状をQGIS上に再現できるようになります。

コメント

このブログの人気の投稿

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

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

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