見出し画像

国交省が公開している都市計画決定GISデータをGoogleマイマップで表示する

目標

国交省が公開している「都市計画決定GISデータ」をGoogleのマイマップで表示する。

サンプル

完成品

全都道府県のkmlファイルをGitHubで公開しています。

変換済の項目は次の通りです。

  • 都市計画区域

  • 区域区分(市街化区域、市街化調整区域)

  • 用途地域(工業専用地域、商業地域等)

データ概要

ダウンロードできるデータ

各都道府県の各市町ごとに、shapeファイルが格納されています。

Googleマイマップでの利用

Googleマイマップのヘルプを確認してみると、shapeファイルはインポートできないとのことでした。

ファイルのインポート
次のいずれかの形式で情報が保存されていることを確認します。CSV
TSV
KML
KMZ
GPX
XLSX
Google スプレッドシート
Google ドライブまたは Google フォトに保存された写真

Google マイマップ ヘルプ

ということで、Googleマイマップで利用できるよう、shapeファイルをkmlファイルに変換していきます。

都市計画区域

shapeファイルの確認

まずはshapeファイルの内容確認から。兵庫県姫路市を例として、shapeファイルを表示してみます。

Jupyter Notebookで作業しています。

import geopandas as gpd
import matplotlib.pyplot as plt
import pandas as pd

# shapeファイルのパスを指定
shapefile_path = '../shape_org/28_兵庫県/28201_姫路市/28201_tokei.shp'

# GeoDataFrameを表示
gdf = gpd.read_file(shapefile_path)
fig, ax = plt.subplots(figsize=(10, 10))
gdf.plot(ax=ax)
plt.show()

# 属性情報をDataFrameとして取得
attr_df = pd.DataFrame(gdf.drop(columns='geometry'))
display(attr_df)

属性情報は次の通りとなっています。

shapeファイルの結合

兵庫県でひとつのkmlファイルとしたいので、全市区町村のshapeファイルを結合します。

28_兵庫県ディレクトリ内にある、_tokei.shpファイルのリストを作成します。

import os
import pprint 
from typing import List

def find_shp_files(root_dir: str, keyword: str ) -> List[str]:
 
    file_list = []
    for root, _, files in os.walk(root_dir):
        for file in files:
            if file.endswith('.shp') and keyword in file:
                file_list.append(os.path.join(root, file))
    return file_list


root_directory = '../shape_org/28_兵庫県'
file_list = find_shp_files(root_directory,'_tokei')

リスト内のshapeをすべて結合して、ひとつのGeoDataFrameを作成します。

import geopandas as gpd
import pandas as pd


def merge_shapefiles(file_list: List[str], encoding: str = 'shift-jis') -> gpd.GeoDataFrame:
    gdfs = []
    for file in file_list:
        gdf = gpd.read_file(file, encoding=encoding)
        # 必要な列のみを保持
        gdf = gdf[['Type', 'Pref', 'Citycode', 'Cityname', 'geometry']]
        gdfs.append(gdf)
    
    merged_gdf = pd.concat(gdfs, ignore_index=True)
    return merged_gdf

merged_gdf = merge_shapefiles(file_list)
    
# GeoDataFrameを表示
fig, ax = plt.subplots(figsize=(10, 10))
merged_gdf .plot(ax=ax)
plt.show()

# 属性情報をDataFrameとして取得
attr_df = pd.DataFrame(merged_gdf.drop(columns='geometry'))
display(attr_df)

結合されていることが確認できました。

kmlファイルへの変換と保存

属性情報の「Type」ごとにファイルに保存したいので、gdfを分割します。(結果は、'都市計画区域'のひとつだけですが)

def split_gdf(gdf):
    # 'Type'の一覧を取得
    list = gdf['Type'].unique()
    
    # GeoDataFrameを分割
    split_gdfs = {i: gdf[gdf['Type'] == i] for i in list}

    return split_gdfs

split_gdfs = split_gdf(merged_gdf)

kmlファイルへ変換して保存するコードを実行します。

import os
import geopandas as gpd
import xml.etree.ElementTree as ET
from shapely.geometry import Polygon, MultiPolygon

def reduce_coordinate_precision(coords, precision):
    return [(round(x, precision), round(y, precision)) for x, y in coords]

def simplify_geometry(geom, tolerance):
    if geom.geom_type == 'Polygon':
        return Polygon(geom.exterior.simplify(tolerance))
    elif geom.geom_type == 'MultiPolygon':
        return MultiPolygon([Polygon(p.exterior.simplify(tolerance)) for p in geom.geoms])
    return geom

def create_kml_polygon(coordinates, name, description, style_url):
    placemark = ET.Element('Placemark')
    ET.SubElement(placemark, 'name').text = name
    ET.SubElement(placemark, 'description').text = description
    ET.SubElement(placemark, 'styleUrl').text = style_url
    
    polygon = ET.SubElement(placemark, 'Polygon')
    outer_boundary = ET.SubElement(polygon, 'outerBoundaryIs')
    linear_ring = ET.SubElement(outer_boundary, 'LinearRing')
    coords = ET.SubElement(linear_ring, 'coordinates')
    
    coord_str = ' '.join([f"{x},{y}" for x, y in coordinates])
    coords.text = coord_str
    
    return placemark

def create_style(style_id, color):
    style = ET.Element('Style', id=style_id)
    line_style = ET.SubElement(style, 'LineStyle')
    ET.SubElement(line_style, 'color').text = color
    ET.SubElement(line_style, 'width').text = '2'
    poly_style = ET.SubElement(style, 'PolyStyle')
    ET.SubElement(poly_style, 'fill').text = '0'  # 塗りつぶしなし
    ET.SubElement(poly_style, 'outline').text = '1'  # 輪郭線あり
    return style

def save_kml(split_gdfs, output_dir, coordinate_precision=5, simplify_tolerance=0.00001):
    os.makedirs(output_dir, exist_ok=True)

    style_ids = {
        '都市計画区域': 'style_1low',
    }

    colors = {
        'style_1low': 'ff404040',  # 濃いグレー
    }

    for key, gdf in split_gdfs.items():
        filename = f"{key.replace(' ', '_')}.kml"
        filepath = os.path.join(output_dir, filename)
        
        gdf_wgs84 = gdf.to_crs("EPSG:4326")
        
        kml = ET.Element('kml', xmlns="http://www.opengis.net/kml/2.2")
        document = ET.SubElement(kml, 'Document')
        
        style_id = style_ids.get(key, f'style_{key}')
        color = colors.get(style_id, 'ff404040')  # デフォルト色も濃いグレーに変更
        document.append(create_style(style_id, color))
        
        for _, row in gdf_wgs84.iterrows():
            geom = simplify_geometry(row['geometry'], simplify_tolerance)
            name = str(key)
            description = f"<![CDATA[<h3>{key}</h3><table border='1'><tr><th>属性</th><th>値</th></tr>"
            for col in ['important_attr1', 'important_attr2']:  # Only include important attributes
                if col in gdf_wgs84.columns:
                    description += f"<tr><td>{col}</td><td>{row[col]}</td></tr>"
            description += "</table>]]>"
            
            style_url = f"#{style_id}"
            
            if geom.geom_type == 'Polygon':
                coords = reduce_coordinate_precision(list(geom.exterior.coords), coordinate_precision)
                placemark = create_kml_polygon(coords, name, description, style_url)
                document.append(placemark)
            elif geom.geom_type == 'MultiPolygon':
                for poly in geom.geoms:
                    coords = reduce_coordinate_precision(list(poly.exterior.coords), coordinate_precision)
                    placemark = create_kml_polygon(coords, name, description, style_url)
                    document.append(placemark)
        
        tree = ET.ElementTree(kml)
        tree.write(filepath, encoding='utf-8', xml_declaration=True)
        
        print(f"保存完了: {filepath}")
    
    print(f"\n全てのKMLファイルが {output_dir} に保存されました。")

# 使用例
output_directory = "./kml_output/01_都市計画区域"
save_kml(split_gdfs, output_directory, coordinate_precision=5, simplify_tolerance=0.00001)

これで無事、kmlファイルが保存されました。

kmlファイルのサイズ制限

Google マイマップにインポートできるkmlファイルは最大5MBまでとなっています。

サイズが大きい場合は、以下の値を大きくすることで調整が可能です。

coordinate_precision=5

このパラメータは、座標の精度を設定します。値が5の場合、小数点以下5桁まで保持されます。

  • 影響:座標の詳細さを制御し、ファイルサイズに直接影響します。

  • 精度:約1.1mの精度(赤道上)

  • 用途:都市計画のような詳細な地図には適していますが、より広域の地図ではさらに小さい値を使用できる場合があります。

simplify_tolerance=0.00001

このパラメータは、ジオメトリの単純化の程度を制御します。

  • 影響:図形の複雑さを減らし、ポイント数を削減してファイルサイズを小さくします。

  • 精度:約1.1mの誤差を許容(赤道上)

  • 用途:都市計画区域のような大きな多角形の境界線に適しています。細かい詳細が重要でない場合に使用します。

区域区分

全市区町村のshapeファイルを結合して、kmlファイルに変換&保存するまでの手順は、ほぼ同じですので省略します。

市街化区域と市街化調整区域の2つのファイルを作成・保存します。
kmlファイルに保存するときに、色指定しています。

def split_gdf(gdf):
    # '区域区分'の一覧を取得
    list = gdf['区域区分'].unique()
    
    # 各区域区分ごとにGeoDataFrameを分割
    split_gdfs = {i: gdf[gdf['区域区分'] == i] for i in list}

    
    return split_gdfs

# 使用例
split_gdfs = split_gdf(merged_gdf)
import os
import geopandas as gpd
import xml.etree.ElementTree as ET

def reduce_coordinate_precision(coords, precision):
    return [(round(x, precision), round(y, precision)) for x, y in coords]

def create_kml_polygon(coordinates, name, description, style_url):
    placemark = ET.Element('Placemark')
    ET.SubElement(placemark, 'name').text = name
    ET.SubElement(placemark, 'description').text = description
    ET.SubElement(placemark, 'styleUrl').text = style_url
    
    polygon = ET.SubElement(placemark, 'Polygon')
    outer_boundary = ET.SubElement(polygon, 'outerBoundaryIs')
    linear_ring = ET.SubElement(outer_boundary, 'LinearRing')
    coords = ET.SubElement(linear_ring, 'coordinates')
    
    coord_str = ' '.join([f"{x},{y},0" for x, y in coordinates])
    coords.text = coord_str
    
    return placemark

def create_style(style_id, color):
    style = ET.Element('Style', id=style_id)
    line_style = ET.SubElement(style, 'LineStyle')
    ET.SubElement(line_style, 'color').text = color
    ET.SubElement(line_style, 'width').text = '2'
    poly_style = ET.SubElement(style, 'PolyStyle')
    ET.SubElement(poly_style, 'color').text = color.replace('ff', '80')  # 50% transparency
    return style

def save_split_gdfs_to_kml(split_gdfs, output_dir, coordinate_precision=6):
    os.makedirs(output_dir, exist_ok=True)
    
    total_gdfs = len(split_gdfs)
    for i, (kubun, gdf) in enumerate(split_gdfs.items(), 1):
        filename = f"{kubun.replace(' ', '_')}.kml"
        filepath = os.path.join(output_dir, filename)
        
        gdf_wgs84 = gdf.to_crs("EPSG:4326")
        
        kml = ET.Element('kml', xmlns="http://www.opengis.net/kml/2.2")
        document = ET.SubElement(kml, 'Document')
        
        # スタイルの定義
        style_ids = {
            '市街化区域': 'style_shigaika',
            '市街化調整区域': 'style_chosei'
        }
        document.append(create_style(style_ids['市街化区域'], 'ff0000ff'))  # 赤色
        document.append(create_style(style_ids['市街化調整区域'], 'ff00ff00'))  # 緑色
        
        for _, row in gdf_wgs84.iterrows():
            geom = row['geometry']
            kuiki_kubun = row['区域区分']
            name = kuiki_kubun
            description = "<![CDATA["
            description += f"<h3>{kuiki_kubun}</h3>"
            description += "<table border='1'><tr><th>属性</th><th>値</th></tr>"
            for col in gdf_wgs84.columns:
                if col not in ['geometry', '区域区分']:
                    description += f"<tr><td>{col}</td><td>{row[col]}</td></tr>"
            description += "</table>]]>"
            
            style_url = f"#{style_ids.get(kuiki_kubun, 'style_shigaika')}"
            
            if geom.geom_type == 'Polygon':
                coords = reduce_coordinate_precision(list(geom.exterior.coords), coordinate_precision)
                placemark = create_kml_polygon(coords, name, description, style_url)
                document.append(placemark)
            elif geom.geom_type == 'MultiPolygon':
                for poly in geom.geoms:
                    coords = reduce_coordinate_precision(list(poly.exterior.coords), coordinate_precision)
                    placemark = create_kml_polygon(coords, name, description, style_url)
                    document.append(placemark)
        
        tree = ET.ElementTree(kml)
        tree.write(filepath, encoding='utf-8', xml_declaration=True)
        
        print(f"保存完了 ({i}/{total_gdfs}): {filepath}")
    
    print(f"\n全ての区域区分のKMLファイルが {output_dir} に保存されました。")

# 使用例
output_directory = "./kml_output/02_区域区分"
save_split_gdfs_to_kml(split_gdfs, output_directory, coordinate_precision=7)

用途地域

こちらも考え方は同じです。用途地域ごとに色指定しています。

import os
import geopandas as gpd
import xml.etree.ElementTree as ET

def reduce_coordinate_precision(coords, precision):
    return [(round(x, precision), round(y, precision)) for x, y in coords]

def create_kml_polygon(coordinates, name, description, style_url):
    placemark = ET.Element('Placemark')
    ET.SubElement(placemark, 'name').text = name
    ET.SubElement(placemark, 'description').text = description
    ET.SubElement(placemark, 'styleUrl').text = style_url
    
    polygon = ET.SubElement(placemark, 'Polygon')
    outer_boundary = ET.SubElement(polygon, 'outerBoundaryIs')
    linear_ring = ET.SubElement(outer_boundary, 'LinearRing')
    coords = ET.SubElement(linear_ring, 'coordinates')
    
    coord_str = ' '.join([f"{x},{y},0" for x, y in coordinates])
    coords.text = coord_str
    
    return placemark

def create_style(style_id, color):
    style = ET.Element('Style', id=style_id)
    line_style = ET.SubElement(style, 'LineStyle')
    ET.SubElement(line_style, 'color').text = color
    ET.SubElement(line_style, 'width').text = '2'
    poly_style = ET.SubElement(style, 'PolyStyle')
    ET.SubElement(poly_style, 'color').text = color.replace('ff', '80')  # 50% transparency
    return style

def save_kml(split_gdfs, output_dir, coordinate_precision=6):
    os.makedirs(output_dir, exist_ok=True)

    # 用途地域ごとのスタイル定義
    style_ids = {
        '第一種低層住居専用地域': 'style_1low',
        '第二種低層住居専用地域': 'style_2low',
        '第一種中高層住居専用地域': 'style_1mid',
        '第二種中高層住居専用地域': 'style_2mid',
        '第一種住居地域': 'style_1res',
        '第二種住居地域': 'style_2res',
        '準住居地域': 'style_semires',
        '近隣商業地域': 'style_neighbor',
        '商業地域': 'style_commercial',
        '準工業地域': 'style_semiindustrial',
        '工業地域': 'style_industrial',
        '工業専用地域': 'style_exclusiveindustrial'
    }

    # 色の定義 (AABBGGRR形式)
    colors = {
        'style_1low': 'ff00ff00',  # 緑
        'style_2low': 'ff90ee90',  # 薄緑
        'style_1mid': 'ff00ff7f',  # 黄緑
        'style_2mid': 'ff98fb98',  # 薄黄緑
        'style_1res': 'ff00ffff',  # 黄
        'style_2res': 'ffffd700',  # 薄オレンジ
        'style_semires': 'ff00a5ff',  # オレンジ
        'style_neighbor': 'ffff69b4',  # ピンク
        'style_commercial': 'ff0000ff',  # 赤
        'style_semiindustrial': 'ffee82ee',  # 紫
        'style_industrial': 'ffffffe0',  # 水色
        'style_exclusiveindustrial': 'ffff0000'  # 青
    }

    # 分割されたGeoDataFrameの各キーに対してKMLを生成
    for key, gdf in split_gdfs.items():
        filename = f"{key.replace(' ', '_')}.kml"
        filepath = os.path.join(output_dir, filename)
        
        gdf_wgs84 = gdf.to_crs("EPSG:4326")
        
        kml = ET.Element('kml', xmlns="http://www.opengis.net/kml/2.2")
        document = ET.SubElement(kml, 'Document')
        
        # スタイルの定義
        style_id = style_ids.get(key, f'style_{key}')
        color = colors.get(style_id, 'ff888888')  # デフォルト色はグレー
        document.append(create_style(style_id, color))
        
        for _, row in gdf_wgs84.iterrows():
            geom = row['geometry']
            name = str(key)
            description = "<![CDATA["
            description += f"<h3>{key}</h3>"
            description += "<table border='1'><tr><th>属性</th><th>値</th></tr>"
            for col in gdf_wgs84.columns:
                if col != 'geometry':
                    description += f"<tr><td>{col}</td><td>{row[col]}</td></tr>"
            description += "</table>]]>"
            
            style_url = f"#{style_id}"
            
            if geom.geom_type == 'Polygon':
                coords = reduce_coordinate_precision(list(geom.exterior.coords), coordinate_precision)
                placemark = create_kml_polygon(coords, name, description, style_url)
                document.append(placemark)
            elif geom.geom_type == 'MultiPolygon':
                for poly in geom.geoms:
                    coords = reduce_coordinate_precision(list(poly.exterior.coords), coordinate_precision)
                    placemark = create_kml_polygon(coords, name, description, style_url)
                    document.append(placemark)
        
        tree = ET.ElementTree(kml)
        tree.write(filepath, encoding='utf-8', xml_declaration=True)
        
        print(f"保存完了: {filepath}")
    
    print(f"\n全てのKMLファイルが {output_dir} に保存されました。")

# 使用例
output_directory = "./kml_output/03_用途地域"
save_kml(split_gdfs, output_directory, coordinate_precision=7)


おわりに

変換コード、および変換後のkmlファイル(全都道府県)をGitHubに公開しています。ご自由に利用ください。


この記事が気に入ったらサポートをしてみませんか?