土石流区域設定支援システムと同等な横断測線、流下方向(縦断測線)をQGISで再現
はじめに
土石流区域設定支援システム(dem.mdb)のCSVデータより、横断測線、縦断測線をQGIS上で可視化・活用するためのPythonスクリプトを作成しました。
システム独自の「カンマ区切り座標」や「mm単位」を自動変換し、以下の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上に再現できるようになります。
コメント
コメントを投稿