国交省が公開している都市計画決定GISデータをGoogleマイマップで表示する
目標
国交省が公開している「都市計画決定GISデータ」をGoogleのマイマップで表示する。
サンプル
完成品
全都道府県のkmlファイルをGitHubで公開しています。
変換済の項目は次の通りです。
都市計画区域
区域区分(市街化区域、市街化調整区域)
用途地域(工業専用地域、商業地域等)
データ概要
ダウンロードできるデータ
各都道府県の各市町ごとに、shapeファイルが格納されています。
Googleマイマップでの利用
Googleマイマップのヘルプを確認してみると、shapeファイルはインポートできないとのことでした。
ということで、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に公開しています。ご自由に利用ください。
この記事が気に入ったらサポートをしてみませんか?