OpenStreetMapのRiver Loacation Coordinate Information

 ALOSでGlobalなGlobalなDigital Elevation ModelをGetできるようになれば、ほかのGeospacial DataについてもTryしたくなり、またまたになりますが、GlobalなRiverについてTry to Evaluateしました。

 Of course, Janpan GovermentのNational Numerical Land Information(国土数値情報)のRiver Informationでは対応できないことから、 約3年前にHelsinki Univ.がOfferしているGEO-PYTHON 2021というEducational SessionでOpenStreetMap(OSM)をUseした記憶を思い起こし、OSMにRiverDataがStoreされているかどうかを、MasterGeminiにAskしてみました。

>>MasterGeminiのAnswer<<

OpenStreetMap (OSM) の川の位置情報データは、ノード、ウェイ、リレーションという3つの主要な要素で構成される地理空間データです。


データの構成要素

  1. ノード (Nodes)

    • OSMの最小単位で、単一の点を表します。

    • 緯度・経度座標を持ち、川の特定の点、例えば水源や合流点、湾曲点などを表現するのに使われます。

  2. ウェイ (Ways)

    • ノードを順番に結んで作られる線を表します。

    • 川の経路や流路を表現するために使われます。川のデータでは、複数のノードが連続して繋がったウェイとして定義されます。

  3. リレーション (Relations)

    • ノード、ウェイ、他のリレーションといった複数の要素をまとめて、より複雑な関係性を定義します。

    • 川のデータでは、複数のウェイが繋がって一本の長い川を構成する場合や、流域全体、水系などを表現するのに使われます。


主なタグ付け

OSMでは、これらの要素にタグと呼ばれるキーと値のペアを付けて、その性質を記述します。川に関する主なタグは以下の通りです。

  • waterway=river: 主要な川の流路を表します。

  • waterway=stream: 小さな流れや小川を表します。

  • waterway=canal: 人工的な運河を表します。

  • name=*: 川の名前(例:name=淀川

  • width=*: 川の幅

  • intermittent=yes: 季節によって水量が変動したり、枯れたりする川を表します。

これらのデータは、OSMのデータベースに格納されており、専用のツールやAPIを通じて取得、利用することができます。

--End--


 By the way, we try to review about OSM. I ask Master Gemini what the OSM is again.

>> Answer from Master Gemini<<
OpenStreetMap(オープンストリートマップ、略称:OSM)は、誰もが自由に利用、編集、共有できるオープンな世界地図を作成することを目的とした、共同プロジェクトです。しばしば「地図版Wikipedia」とも呼ばれます。

主な特徴

  • コミュニティ主導のプロジェクト: 世界中のボランティア(マッパー)が、GPS機器、空中写真、現地調査などを通じて得た情報を基に地図を作成・更新しています。新しい道路や建物ができた際も、ユーザーがリアルタイムに情報を反映させることができます。

  • オープンデータ: 地図データ自体がオープンライセンス(ODbL)で提供されており、個人利用だけでなく、商業利用も含めて誰でも無料で利用できます。商用地図サービスとは異なり、利用料金や制限がほとんどありません。

  • 詳細で多岐にわたる情報: 道路、建物、鉄道、河川といった基本的な地理情報だけでなく、ベンチ、ゴミ箱、郵便ポスト、バリアフリー情報など、細かな情報までタグ付けされています。これにより、特定の用途に特化した地図を作成することが可能です。

  • データの自由なカスタマイズ: OSMは単なる地図画像ではなく、地理空間データそのものです。このデータは様々な形式でダウンロード可能であり、開発者や研究者が独自のアプリケーションやサービスを構築する際に、自由に加工・利用することができます。

利用方法

OpenStreetMapのデータや地図は、様々な方法で利用されています。

  1. 地図として閲覧する: 公式ウェブサイトや、OSMデータを利用している様々なアプリケーションで、地図を閲覧することができます。

  2. データとして利用する: OSMのデータは、ノード、ウェイ、リレーションという3つの要素で構成されており、これらをダウンロードして、GISソフトウェアやプログラミングで分析・可視化することができます。

  3. 編集に参加する: アカウントを作成すれば、誰でも地図の編集に参加できます。身の回りの情報を追加したり、既存の情報を修正したりすることで、地図の精度向上に貢献できます。

ライセンスについて

OpenStreetMapの地理データは、Open Database License (ODbL)というライセンスで提供されています。これにより、データの利用、複製、配布、改変が許可されています。ただし、利用する際には、OpenStreetMapへのクレジット表示(© OpenStreetMap contributors)が必要です。

OpenStreetMapは、多様なアプリケーションやサービスの基盤として、ナビゲーション、災害時の情報共有、学術研究など、幅広い分野で活用されています。

 OSM has excellent concept. I try to get River Coordinate data(lon, lat) from OSM by Pythom Code. But, So that OSM avoid Server's heavy and over load, we should take access that have resonable interval. 
 まず最初は香川県にある二級河川KoutouRiverのLocation Informationを取得して、FoliumでMapにShowするCodeをGemini先生にRequestしました。Codeはlike belowです。
>>Code<<
import requests
import pandas as pd
import folium
import warnings

# warnings を設定し、DeprecationWarning を表示しないようにします。
warnings.filterwarnings("ignore", category=DeprecationWarning)

def get_rivers_geojson_data(region_name, bbox):
    """
    指定された地域(バウンディングボックス)内の河川情報を直接API呼び出しで取得し、
    GeoJSON形式に適した座標リストを返す関数

    Args:
        region_name (str): 地域の名前(例: '香東川')
        bbox (tuple): 地域のバウンディングボックス (min_lat, min_lon, max_lat, max_lon)

    Returns:
        list: 河川のGeoJSON 'LineString' 形式に適した座標リストとプロパティのリスト
              例: [{'coordinates': [[lat, lon], [lat, lon], ...], 'name': '香東川', ...}]
    """
    print(f"[{region_name}] 河川データの取得を開始します...")

    # bbox (min_lat, min_lon, max_lat, max_lon) から south, west, north, east を抽出
    south, west, north, east = bbox[0], bbox[1], bbox[2], bbox[3]

    # Overpass QLクエリ
    # name タグを明示的に指定します。
    overpass_query = f"""
    [out:json][timeout:180];
    (
      way["waterway"~"river|stream"]["name"="香東川"]({south},{west},{north},{east});
      relation["waterway"~"river|stream"]["name"="香東川"]({south},{west},{north},{east});
    );
    out body;
    >;
    out skel qt;
    """

    overpass_url = "https://overpass-api.de/api/interpreter"

    try:
        response = requests.post(overpass_url, data=overpass_query)
        response.raise_for_status() # HTTPエラーが発生した場合に例外を発生させる
        data = response.json()
    except requests.exceptions.RequestException as e:
        print(f"[{region_name}] APIリクエスト中にエラーが発生しました: {e}")
        return []
    except Exception as e:
        print(f"[{region_name}] データ取得中に予期せぬエラーが発生しました: {e}")
        return []

    river_lines_data = []

    # 取得したノードを効率的に検索するための辞書を作成
    nodes_map = {el['id']: el for el in data.get('elements', []) if el['type'] == 'node'}

    for element in data.get("elements", []):
        element_type = element.get("type")
        osm_id = element.get("id")
        name = element.get("tags", {}).get("name")
        waterway_type = element.get("tags", {}).get("waterway")

        # nameが'香東川'で、かつwayまたはrelationの場合のみ処理
        if name == "香東川" and element_type in ["way", "relation"]:
            current_coordinates_list = [] # この河川(way/relation)の全ての座標を保持するリスト

            if element_type == "way":
                if "nodes" in element:
                    for node_id in element["nodes"]:
                        node_info = nodes_map.get(node_id)
                        if node_info:
                            current_coordinates_list.append([node_info["lat"], node_info["lon"]])
            elif element_type == "relation":
                # relationの場合、メンバー(way)からノード情報を収集する
                # 複雑なrelation(複数のwayで構成される河川など)に対応
                if "members" in element:
                    for member in element["members"]:
                        if member["type"] == "way":
                            way_info = next((el for el in data.get('elements', []) if el.get('type') == 'way' and el.get('id') == member['ref']), None)
                            if way_info and "nodes" in way_info:
                                member_coords = []
                                for node_id in way_info["nodes"]:
                                    node_info = nodes_map.get(node_id)
                                    if node_info:
                                        member_coords.append([node_info["lat"], node_info["lon"]])
                                if member_coords:
                                    # 関係のwayは順序を持つ場合があるため、適切な順序で結合が必要
                                    # ここでは単純にリストに追加。より複雑な結合ロジックは別途必要。
                                    current_coordinates_list.extend(member_coords)

            # 座標が取得できた場合のみデータを追加
            if current_coordinates_list:
                river_lines_data.append({
                    "osm_id": osm_id,
                    "name": name,
                    "waterway_type": waterway_type,
                    "type": element_type,
                    "coordinates": current_coordinates_list # 全ての点のリスト
                })

    print(f"[{region_name}] {len(river_lines_data)} 件の河川(線)データを取得しました。")
    return river_lines_data

# --- 香東川に限定したバウンディングボックス ---
# 香東川の流路をほぼ完全にカバーするように調整した範囲です。
# 南緯, 西経, 北緯, 東経 の順です。
regions = {
    "香東川": (34.13, 133.91, 34.36, 134.09) # この座標は前回と同様で、Overpass Turboで確認済み
}

all_river_lines = []

for region_name, bbox in regions.items():
    lines_data = get_rivers_geojson_data(region_name, bbox)
    if lines_data:
        all_river_lines.extend(lines_data)

# 重複する河川がある場合、OSM IDで重複を排除します。
# 辞書のリストから重複を削除するため、一度セットに変換してから戻す
# ただし、同じwayが複数のリレーションに属する場合など、完全に一意にするのは複雑
# ここでは、簡略化のため、osm_idとtypeのタプルで比較します
unique_river_lines = {}
for line in all_river_lines:
    key = (line['osm_id'], line['type'])
    if key not in unique_river_lines:
        unique_river_lines[key] = line
final_river_lines = list(unique_river_lines.values())


print("\n--- 全体の結果 ---")
print(f"合計 {len(final_river_lines)} 件の河川(線)データを描画します。")

# Foliumマップの初期化 (香東川の中心付近を中心に)
# 取得したデータの中心を使うか、香東川の概ねの中心を設定
if final_river_lines:
    # 全ての座標をフラットにして中心を計算(少し重い可能性あり)
    all_coords_flat = [coord for line in final_river_lines for coord in line['coordinates']]
    center_lat = sum([c[0] for c in all_coords_flat]) / len(all_coords_flat)
    center_lon = sum([c[1] for c in all_coords_flat]) / len(all_coords_flat)
else:
    # データがない場合、香東川のデフォルト中心座標
    center_lat = 34.25
    center_lon = 134.00

m = folium.Map(location=[center_lat, center_lon], zoom_start=11)

# 各河川のPolylineを地図に追加
for river_line in final_river_lines:
    folium.PolyLine(
        locations=river_line['coordinates'], # 緯度経度ペアのリスト
        color="blue",
        weight=3,
        opacity=0.7,
        tooltip=f"ID: {river_line['osm_id']}<br>名称: {river_line.get('name', 'N/A')}<br>タイプ: {river_line['waterway_type']}"
    ).add_to(m)

# 地図をHTMLファイルとして保存
map_output_path = "koto_river_polyline_map.html"
m.save(map_output_path)
print(f"\n香東川の河川データ(Polyline)の地図は '{map_output_path}' に保存されました。ブラウザで開いてください。")
m

>>Output<<

 OSMからGetした座標をLineでConnectしているだけなので、へんなStraightLineが散見されるなど、Plot showとしては改善の余地ありです。Moreover, River lines is not accurate and show not all sube river of Koutou River. Then we search code where Kotou River designation. It's probably below.

# --- 香東川に限定したバウンディングボックス ---
# 香東川の流路をほぼ完全にカバーするように調整した範囲です。
# 南緯, 西経, 北緯, 東経 の順です。
regions = {
    "香東川": (34.13, 133.91, 34.36, 134.09) # この座標は前回と同様で、Overpass Turboで確認済み
}

 It designates Kotou River Area by lon, lat. We need to enlarge this area. The improved code is below.


# --- 香東川とその周辺(支流も含む)のバウンディングボックス ---
# 香東川の流路と主要な支流をカバーするように調整した範囲です。
# 前回よりやや広めに設定し、香東川水系を網羅するイメージです。
# 南緯, 西経, 北緯, 東経 の順です。
regions = {
    "香東川水系": (34.05, 133.85, 34.40, 134.15) # 香東川の範囲を少し広げ、周辺の支流もカバー
}


 The output is below.


 The result showed by Follium, is include Yoshinogawa River and others with Kotou River. I could allow this plot. I awared what the code include extend() function. It is defferent from append() function.
 I asked the difference between two functions for Master Gemini.

>>Answer from Master Gemini<<

append()extend() はどちらもリストに要素を追加するメソッドですが、その挙動に大きな違いがあります。

append(): 単一の要素を追加する

append()は、引数として渡されたオブジェクトを単一の要素として、リストの末尾に追加します。引数がリストであっても、そのリスト全体が1つの要素として追加されます。

Python
my_list = [1, 2, 3]
new_items = [4, 5]
my_list.append(new_items)
print(my_list)
# 出力: [1, 2, 3, [4, 5]]

extend(): 複数の要素を追加する

extend()は、引数として渡されたイテラブル(リストやタプルなど)の各要素を個別に、リストの末尾に追加します。

Python
my_list = [1, 2, 3]
new_items = [4, 5]
my_list.extend(new_items)
print(my_list)
# 出力: [1, 2, 3, 4, 5]

まとめ

  • append(): リストの中に別のリストを入れたい場合。

  • extend(): リストの要素を増やしたい場合。

この違いを理解しておくと、リスト操作を行う際に意図しない結果になることを防ぐことができます。

 微妙なDifferenceにSeemかもしれませんが、これって結構ImportantなIssueをintendしています。実は、A few years agoに国土数値情報の河川情報をDownloadして分析したことがあるのですが、この河川情報は確か本支川ごとのCSV形式で保管されており、本支川ごとにGetしたCoordinateのListをわざわざfor文で分解して、一つずつのCoordinateを新たに用意した水系全体のCoordinateをStoreするList変数にAppendするというOperationをしていました。extend()関数があれば、ForRoopをUseすることなく、水系全体のCoordinateListを作成できていたはずです。
 
 次に、RiverNodeのCoordinateをLineでつなぐと、水系がJumpしたときに不連続な直線が描かれてしまうので、LineではなくDotを使って描画するようにReviceします。

import requests
import pandas as pd
import folium
import warnings

# warnings を設定し、DeprecationWarning を表示しないようにします。
warnings.filterwarnings("ignore", category=DeprecationWarning)

def get_rivers_nodes_data(region_name, bbox):
    """
    指定された地域(バウンディングボックス)内の河川(本流・支流)情報を
    直接API呼び出しで取得し、その河川を構成する全てのノードの座標リストを返す関数。

    Args:
        region_name (str): 地域の名前(例: '香東川とその支流のノード')
        bbox (tuple): 地域のバウンディングボックス (min_lat, min_lon, max_lat, max_lon)

    Returns:
        list: 河川のノードの辞書リスト。
              例: [{'lat': lat, 'lon': lon, 'river_osm_id': way_id, 'river_name': '香東川', 'waterway_type': 'river'}]
    """
    print(f"[{region_name}] 河川ノードデータの取得を開始します...")

    # bbox (min_lat, min_lon, max_lat, max_lon) から south, west, north, east を抽出
    south, west, north, east = bbox[0], bbox[1], bbox[2], bbox[3]

    # Overpass QLクエリ
    # name タグによるフィルタを削除し、bbox内の全てのriver/streamを取得
    overpass_query = f"""
    [out:json][timeout:180];
    (
      way["waterway"~"river|stream"]({south},{west},{north},{east});
      relation["waterway"~"river|stream"]({south},{west},{north},{east});
    );
    out body;
    >;
    out skel qt;
    """

    overpass_url = "https://overpass-api.de/api/interpreter"

    try:
        # POST リクエストでクエリを送信
        response = requests.post(overpass_url, data=overpass_query)
        response.raise_for_status() # HTTPエラーが発生した場合に例外を発生させる
        data = response.json()
    except requests.exceptions.RequestException as e:
        print(f"[{region_name}] APIリクエスト中にエラーが発生しました: {e}")
        return []
    except Exception as e:
        print(f"[{region_name}] データ取得中に予期せぬエラーが発生しました: {e}")
        return []

    river_nodes_data = []

    # 取得したノードを効率的に検索するための辞書を作成
    nodes_map = {el['id']: el for el in data.get('elements', []) if el['type'] == 'node'}

    # 各wayやrelationからノード情報を抽出
    for element in data.get("elements", []):
        element_type = element.get("type")
        osm_id = element.get("id")
        name = element.get("tags", {}).get("name", "名称なし")
        waterway_type = element.get("tags", {}).get("waterway")

        if waterway_type in ["river", "stream"] and element_type in ["way", "relation"]:
            node_ids_to_process = []
            if element_type == "way":
                if "nodes" in element:
                    node_ids_to_process = element["nodes"]
            elif element_type == "relation":
                if "members" in element:
                    for member in element["members"]:
                        if member["type"] == "way":
                            way_info = next((el for el in data.get('elements', []) if el.get('type') == 'way' and el.get('id') == member['ref']), None)
                            if way_info and "nodes" in way_info:
                                node_ids_to_process.extend(way_info["nodes"])

            # 抽出したノードIDから座標と情報を取得し、リストに追加
            for node_id in set(node_ids_to_process): # 重複ノードを避けるためsetを使用
                node_info = nodes_map.get(node_id)
                if node_info:
                    river_nodes_data.append({
                        "lat": node_info["lat"],
                        "lon": node_info["lon"],
                        "node_id": node_id, # 各ノードのID
                        "river_osm_id": osm_id, # そのノードが属する河川のway/relation ID
                        "river_name": name,
                        "waterway_type": waterway_type
                    })

    # ノードレベルでの重複排除も行っておく(同じ座標に複数のノードがある場合など)
    # pandas DataFrameに変換して、緯度経度で重複を排除
    df_nodes = pd.DataFrame(river_nodes_data)
    if not df_nodes.empty:
        df_nodes.drop_duplicates(subset=['lat', 'lon'], inplace=True)
        river_nodes_data = df_nodes.to_dict(orient='records')


    print(f"[{region_name}] {len(river_nodes_data)} 件の河川ノードデータを取得しました。")
    return river_nodes_data

# --- 香東川とその周辺(支流も含む)のバウンディングボックス ---
# 前回と同様の範囲を使用します。
# 南緯, 西経, 北緯, 東経 の順です。
regions = {
    "香東川水系": (34.05, 133.85, 34.40, 134.15) # 香東川の範囲を少し広げ、周辺の支流もカバー
}

all_river_nodes = []

for region_name, bbox in regions.items():
    nodes_data = get_rivers_nodes_data(region_name, bbox)
    if nodes_data:
        all_river_nodes.extend(nodes_data)

print("\n--- 全体の結果 ---")
print(f"合計 {len(all_river_nodes)} 件の河川ノードデータを描画します。")

# Foliumマップの初期化 (香東川水系の中心付近を中心に)
if all_river_nodes:
    # 全てのノードの緯度経度から中心を計算
    center_lat = sum([n['lat'] for n in all_river_nodes]) / len(all_river_nodes)
    center_lon = sum([n['lon'] for n in all_river_nodes]) / len(all_river_nodes)
else:
    # データがない場合、香東川のデフォルト中心座標
    center_lat = 34.25
    center_lon = 134.00

m = folium.Map(location=[center_lat, center_lon], zoom_start=11)

# 各ノードをCircleMarkerとして地図に追加
for node in all_river_nodes:
    # 河川のタイプによって色を変える
    marker_color = "blue"
    if node.get('waterway_type') == 'stream':
        marker_color = "darkblue" # 小川のノードは少し濃い色に

    folium.CircleMarker(
        location=[node['lat'], node['lon']],
        radius=0.5, # マーカーのサイズを小さくする
        color=marker_color,
        fill=True,
        fill_color=marker_color,
        fill_opacity=0.5,
        tooltip=f"ノードID: {node['node_id']}<br>河川名: {node.get('river_name', 'N/A')}<br>タイプ: {node['waterway_type']}"
    ).add_to(m)

# 地図をHTMLファイルとして保存
map_output_path = "koto_river_nodes_map.html"
m.save(map_output_path)
print(f"\n香東川水系の河川ノードデータ(点)の地図は '{map_output_path}' に保存されました。ブラウザで開いてください。")
m


 OpenStreetMapのRiverNodeDataをDownloadするCaseでは、DesignateしたAreaをPassOnするRiverのInformationがGetできるようなFillingでございますね。この点も頭の片隅に置きながら、これをExpandして、四国全体のRiveNodeをGetしたうえで、FoliumMap上にDotで表示するCodeを最後にCreatしておきます。
 A Number of NodesがLargeすぎて、RAMがOverFlowして、GoogleColabがStopするため、RiverNodeを10個ずつOmitして、FoliumMapにShowするDotsを10分の1にDecreaseするOrderもIncludeしています。

import requests
import pandas as pd
import folium
import warnings
import time

# warnings を設定し、DeprecationWarning を表示しないようにします。
warnings.filterwarnings("ignore", category=DeprecationWarning)

def get_rivers_nodes_data(region_name, bbox):
    """
    指定された地域(バウンディングボックス)内の河川(本流・支流)情報を
    直接API呼び出しで取得し、その河川を構成する全てのノードの座標リストを返す関数。
    (この関数は元のコードと同じです)
    """
    print(f"[{region_name}] 河川ノードデータの取得を開始します...")
    south, west, north, east = bbox[0], bbox[1], bbox[2], bbox[3]
    overpass_query = f"""
    [out:json][timeout:180];
    (
      way["waterway"~"river|stream"]({south},{west},{north},{east});
      relation["waterway"~"river|stream"]({south},{west},{north},{east});
    );
    out body;
    >;
    out skel qt;
    """
    overpass_url = "https://overpass-api.de/api/interpreter"
    try:
        response = requests.post(overpass_url, data=overpass_query)
        response.raise_for_status()
        data = response.json()
    except requests.exceptions.RequestException as e:
        print(f"[{region_name}] APIリクエスト中にエラーが発生しました: {e}")
        return []
    except Exception as e:
        print(f"[{region_name}] データ取得中に予期せぬエラーが発生しました: {e}")
        return []
    river_nodes_data = []
    nodes_map = {el['id']: el for el in data.get('elements', []) if el['type'] == 'node'}
    for element in data.get("elements", []):
        element_type = element.get("type")
        osm_id = element.get("id")
        name = element.get("tags", {}).get("name", "名称なし")
        waterway_type = element.get("tags", {}).get("waterway")
        if waterway_type in ["river", "stream"] and element_type in ["way", "relation"]:
            node_ids_to_process = []
            if element_type == "way":
                if "nodes" in element:
                    node_ids_to_process = element["nodes"]
            elif element_type == "relation":
                if "members" in element:
                    for member in element["members"]:
                        if member["type"] == "way":
                            way_info = next((el for el in data.get('elements', []) if el.get('type') == 'way' and el.get('id') == member['ref']), None)
                            if way_info and "nodes" in way_info:
                                node_ids_to_process.extend(way_info["nodes"])
            for node_id in set(node_ids_to_process):
                node_info = nodes_map.get(node_id)
                if node_info:
                    river_nodes_data.append({
                        "lat": node_info["lat"],
                        "lon": node_info["lon"],
                        "node_id": node_id,
                        "river_osm_id": osm_id,
                        "river_name": name,
                        "waterway_type": waterway_type
                    })
    df_nodes = pd.DataFrame(river_nodes_data)
    if not df_nodes.empty:
        df_nodes.drop_duplicates(subset=['lat', 'lon'], inplace=True)
        river_nodes_data = df_nodes.to_dict(orient='records')
    print(f"[{region_name}] {len(river_nodes_data)} 件の河川ノードデータを取得しました。")
    return river_nodes_data

# --- メイン処理部分 ---

# 四国全体のバウンディングボックス
shikoku_bbox = (32.8, 132.0, 34.4, 134.8)

# 四国全体を分割するための設定
lat_divisions = 5
lon_divisions = 5
min_lat, min_lon, max_lat, max_lon = shikoku_bbox
lat_step = (max_lat - min_lat) / lat_divisions
lon_step = (max_lon - min_lon) / lon_divisions

all_river_nodes = []

print("--- 四国全体の河川ノードデータ取得を開始します ---")

for i in range(lat_divisions):
    for j in range(lon_divisions):
        sub_bbox = (
            min_lat + i * lat_step,
            min_lon + j * lon_step,
            min_lat + (i + 1) * lat_step,
            min_lon + (j + 1) * lon_step
        )
        region_name = f"四国エリア_{i}_{j}"
        nodes_data = get_rivers_nodes_data(region_name, sub_bbox)
        if nodes_data:
            all_river_nodes.extend(nodes_data)
        print("サーバー負荷軽減のため、5秒間待機します...")
        time.sleep(5)

print("\n--- 全体の結果 ---")
# 取得したノードデータ全体から重複を排除
df_final = pd.DataFrame(all_river_nodes)
if not df_final.empty:
    df_final.drop_duplicates(subset=['lat', 'lon'], inplace=True, ignore_index=True)
    all_river_nodes = df_final.to_dict(orient='records')

print(f"合計 {len(all_river_nodes)} 件の河川ノードデータを取得しました。")

# --- ここから間引きのロジックを追加 ---
thinning_factor = 10 # 10ノードに1つだけ残す
thinned_nodes = all_river_nodes[::thinning_factor]
print(f"データ解像度を調整するため、ノードデータを間引きました。")
print(f"元データ数: {len(all_river_nodes)} -> 描画ノード数: {len(thinned_nodes)}")

# Foliumマップの初期化 (四国全体が収まるように)
center_lat = (min_lat + max_lat) / 2
center_lon = (min_lon + max_lon) / 2
m = folium.Map(location=[center_lat, center_lon], zoom_start=9)

# 間引いたノードをCircleMarkerとして地図に追加
for node in thinned_nodes: # ここで 'thinned_nodes' を使用
    marker_color = "blue"
    if node.get('waterway_type') == 'stream':
        marker_color = "darkblue"
    folium.CircleMarker(
        location=[node['lat'], node['lon']],
        radius=0.5,
        color=marker_color,
        fill=True,
        fill_color=marker_color,
        fill_opacity=0.5,
        tooltip=f"ノードID: {node['node_id']}<br>河川名: {node.get('river_name', 'N/A')}<br>タイプ: {node['waterway_type']}"
    ).add_to(m)

# 地図をHTMLファイルとして保存
map_output_path = "shikoku_thinned_river_nodes_map.html"
m.save(map_output_path)
print(f"\n間引かれた河川ノードデータ(点)の地図は '{map_output_path}' に保存されました。ブラウザで開いてください。")

最後に、「m」だけをCodeにTypeしてRunするとLikeBelowのMapがShowされます。


コメント