Spaces:
Sleeping
Sleeping
import streamlit as st | |
import matplotlib.pyplot as plt | |
import osmnx as ox | |
import geopandas as gpd | |
import pandas as pd | |
import io | |
from PIL import Image | |
import numpy as np | |
import hashlib | |
import shapely | |
ox.settings.use_cache = True # OSMnxのキャッシュを有効化 | |
def meters_to_degrees(meters, lat): | |
""" | |
距離(メートル)を緯度・経度の変化量(度)に変換する。 | |
""" | |
lat_deg = meters / 111320 | |
lon_deg = meters / (111320 * np.cos(np.radians(lat))) | |
return lat_deg, lon_deg | |
def polygon_hasher(polygon): | |
""" | |
Shapely geometry objectsをハッシュ化するカスタム関数。 | |
""" | |
return hashlib.sha256(polygon.wkb).hexdigest() | |
def fetch_osm_data(point=None, distance=None, polygon=None): | |
""" | |
指定した地点と距離、またはポリゴンに基づいて、OSMデータ(ノード、エッジ、フィーチャ)を取得する。 | |
""" | |
tags = { | |
'building': True, | |
'natural': True, | |
'landuse': True, | |
'leisure': True, | |
'amenity': True, | |
'waterway': True, | |
'aeroway': True, | |
'man_made': True, | |
'railway': True, | |
'highway': True | |
} | |
if polygon is not None: | |
# ポリゴン内のデータを取得 | |
G = ox.graph_from_polygon(polygon, network_type='all') | |
nodes, edges = ox.graph_to_gdfs(G) | |
features = ox.geometries_from_polygon(polygon, tags) | |
elif point is not None and distance is not None: | |
# 指定した地点と距離に基づいてデータを取得 | |
G = ox.graph_from_point(point, dist=distance, network_type='all') | |
nodes, edges = ox.graph_to_gdfs(G) | |
features = ox.geometries_from_point(point, tags, dist=distance) | |
else: | |
st.error("地点と距離、またはポリゴンを指定してください。") | |
return None, None, None | |
return nodes, edges, features | |
def create_artistic_map(lat=None, lon=None, distance=None, polygon=None, dpi=300, width=20, height=20, colors={}): | |
""" | |
指定されたパラメータに基づいて、カスタマイズ可能なアートマップ画像を作成する。 | |
""" | |
# カラー設定の取得 | |
bg_color = colors['bg_color'] | |
greenery_color = colors['greenery_color'] | |
water_color = colors['water_color'] | |
building_colors = colors['building_colors'] | |
major_road_color = colors['major_road_color'] | |
medium_road_color = colors['medium_road_color'] | |
minor_road_color = colors['minor_road_color'] | |
railway_color = colors.get('railway_color', '#000000') | |
amenity_color = colors.get('amenity_color', '#FFD700') | |
waterway_color = colors.get('waterway_color', '#1E90FF') | |
aeroway_color = colors.get('aeroway_color', '#8A2BE2') | |
man_made_color = colors.get('man_made_color', '#FF69B4') | |
if polygon is not None: | |
# ポリゴンを使用してデータを取得 | |
nodes, edges, features = fetch_osm_data(polygon=polygon) | |
# 表示範囲の設定 | |
minx, miny, maxx, maxy = polygon.bounds | |
xlim = (minx, maxx) | |
ylim = (miny, maxy) | |
elif lat is not None and lon is not None and distance is not None: | |
# 地点と距離を使用してデータを取得 | |
point = (lat, lon) | |
nodes, edges, features = fetch_osm_data(point=point, distance=distance) | |
# 距離をもとに緯度経度の幅と高さを計算 | |
lat_deg, lon_deg = meters_to_degrees(distance, lat) | |
xlim = (lon - lon_deg, lon + lon_deg) | |
ylim = (lat - lat_deg, lat + lat_deg) | |
else: | |
st.error("緯度・経度と距離、またはポリゴンを指定してください。") | |
return None | |
# 建物データ取得 | |
if 'building' in features.columns: | |
buildings = features[features['building'].notnull()] | |
else: | |
buildings = gpd.GeoDataFrame() | |
# 水域データ取得 | |
if 'natural' in features.columns: | |
water = features[features['natural'] == 'water'] | |
# 緑地データ取得(自然地形など) | |
greenery = features[(features['natural'].notnull()) & (features['natural'] != 'water')] | |
else: | |
water = gpd.GeoDataFrame() | |
greenery = gpd.GeoDataFrame() | |
# その他のフィーチャの取得 | |
if 'amenity' in features.columns: | |
amenities = features[features['amenity'].notnull()] | |
else: | |
amenities = gpd.GeoDataFrame() | |
if 'man_made' in features.columns: | |
man_made = features[features['man_made'].notnull()] | |
else: | |
man_made = gpd.GeoDataFrame() | |
if 'aeroway' in features.columns: | |
aeroways = features[features['aeroway'].notnull()] | |
else: | |
aeroways = gpd.GeoDataFrame() | |
if 'waterway' in features.columns: | |
waterways = features[features['waterway'].notnull()] | |
else: | |
waterways = gpd.GeoDataFrame() | |
# 描画設定 | |
fig, ax = plt.subplots(figsize=(width, height)) | |
fig.patch.set_facecolor(bg_color) | |
ax.set_facecolor(bg_color) | |
# 緑地の描画 | |
if not greenery.empty: | |
greenery.plot(ax=ax, facecolor=greenery_color, edgecolor='none') | |
# 水域の描画 | |
if not water.empty: | |
water.plot(ax=ax, facecolor=water_color, edgecolor='none') | |
# 建物の描画 | |
if not buildings.empty: | |
building_types = buildings['building'].unique() | |
for i, building_type in enumerate(building_types): | |
building_subset = buildings[buildings['building'] == building_type] | |
building_color = building_colors[i % len(building_colors)] | |
building_subset.plot(ax=ax, facecolor=building_color, edgecolor='none') | |
# アメニティの描画 | |
if not amenities.empty: | |
amenities.plot(ax=ax, facecolor=amenity_color, edgecolor='none') | |
# 人工構造物の描画 | |
if not man_made.empty: | |
man_made.plot(ax=ax, facecolor=man_made_color, edgecolor='none') | |
# 航空施設の描画 | |
if not aeroways.empty: | |
aeroways.plot(ax=ax, facecolor=aeroway_color, edgecolor='none') | |
# 水路の描画 | |
if not waterways.empty: | |
waterways.plot(ax=ax, facecolor=waterway_color, edgecolor='none') | |
# 道路の描画 | |
if not edges.empty and 'highway' in edges.columns: | |
# 主要道路の描画 | |
major_roads = edges[edges['highway'].isin(['motorway', 'trunk', 'primary'])] | |
if not major_roads.empty: | |
major_roads.plot(ax=ax, linewidth=2, edgecolor=major_road_color) | |
# 中程度の道路の描画 | |
medium_roads = edges[edges['highway'].isin(['secondary', 'tertiary'])] | |
if not medium_roads.empty: | |
medium_roads.plot(ax=ax, linewidth=1.5, edgecolor=medium_road_color) | |
# 小規模な道路の描画 | |
minor_roads = edges[~edges['highway'].isin(['motorway', 'trunk', 'primary', 'secondary', 'tertiary'])] | |
if not minor_roads.empty: | |
minor_roads.plot(ax=ax, linewidth=1, edgecolor=minor_road_color) | |
# 鉄道の描画 | |
if not edges.empty and 'railway' in edges.columns: | |
railways = edges[edges['railway'].notnull()] | |
if not railways.empty: | |
railways.plot(ax=ax, linewidth=1.5, edgecolor=railway_color) | |
# 軸をオフに設定 | |
ax.axis('off') | |
# 表示範囲を設定 | |
ax.set_xlim(xlim) | |
ax.set_ylim(ylim) | |
# 画像をバッファに保存 | |
buf = io.BytesIO() | |
plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0, dpi=dpi) | |
plt.close() | |
buf.seek(0) | |
return Image.open(buf) | |
# Streamlit UI | |
st.title("カラフルな地図が作成できます") | |
# スケールの選択 | |
scale_type = st.selectbox("スケールを選択してください", ["ローカル(距離指定)", "行政区域"]) | |
default_lat = 35.533283 | |
default_lon = 139.642000 | |
default_distance = 1000 | |
if scale_type == "ローカル(距離指定)": | |
# ユーザー入力 | |
location = st.text_input("場所を入力してください(住所、都市名など)") | |
if location: | |
try: | |
geocode_result = ox.geocode(location) | |
lat, lon = geocode_result[0], geocode_result[1] | |
st.write(f"緯度: {lat}, 経度: {lon}") | |
except Exception as e: | |
st.error("場所の取得に失敗しました。緯度と経度を直接入力してください。") | |
lat = st.number_input("緯度を入力してください", value=default_lat, format="%.6f") | |
lon = st.number_input("経度を入力してください", value=default_lon, format="%.6f") | |
else: | |
lat = st.number_input("緯度を入力してください", value=default_lat, format="%.6f") | |
lon = st.number_input("経度を入力してください", value=default_lon, format="%.6f") | |
distance = st.slider("距離を選択(メートル)", 100, 50000, default_distance) | |
polygon = None | |
elif scale_type == "行政区域": | |
# 行政区域の入力 | |
admin_area = st.text_input("行政区域名を入力してください(例:東京都、神奈川県、日本)") | |
if admin_area: | |
try: | |
# 行政区域のポリゴンを取得 | |
area_gdf = ox.geocode_to_gdf(admin_area) | |
polygon = area_gdf['geometry'].iloc[0] | |
st.write(f"{admin_area} の領域を取得しました。") | |
except Exception as e: | |
st.error("行政区域の取得に失敗しました。") | |
polygon = None | |
else: | |
st.warning("行政区域名を入力してください。") | |
polygon = None | |
# 緯度・経度・距離は不要 | |
lat = lon = distance = None | |
# カラーテーマの設定 | |
color_themes = { | |
'テーマ1': { | |
'bg_color': '#FAF3E0', | |
'greenery_color': '#80C341', | |
'water_color': '#45A6FF', | |
'building_colors': ['#FF6F61', '#FFAB73', '#FFA07A', '#FFD700', '#F08080'], | |
'major_road_color': '#FF6347', | |
'medium_road_color': '#FF4500', | |
'minor_road_color': '#D2691E', | |
'railway_color': '#8B008B', | |
'amenity_color': '#FFD700', | |
'waterway_color': '#1E90FF', | |
'aeroway_color': '#8A2BE2', | |
'man_made_color': '#FF69B4' | |
}, | |
'テーマ2': { | |
'bg_color': '#FFFFFF', | |
'greenery_color': '#00FF00', | |
'water_color': '#0000FF', | |
'building_colors': ['#A52A2A', '#8B0000', '#B22222', '#FF0000', '#FF6347'], | |
'major_road_color': '#000000', | |
'medium_road_color': '#2F4F4F', | |
'minor_road_color': '#696969', | |
'railway_color': '#000000', | |
'amenity_color': '#FFD700', | |
'waterway_color': '#1E90FF', | |
'aeroway_color': '#8A2BE2', | |
'man_made_color': '#FF69B4' | |
}, | |
'テーマ3': { | |
'bg_color': '#2E3440', | |
'greenery_color': '#A3BE8C', | |
'water_color': '#5E81AC', | |
'building_colors': ['#BF616A', '#D08770', '#EBCB8B', '#A3BE8C', '#B48EAD'], | |
'major_road_color': '#88C0D0', | |
'medium_road_color': '#81A1C1', | |
'minor_road_color': '#5E81AC', | |
'railway_color': '#ECEFF4', | |
'amenity_color': '#FFD700', | |
'waterway_color': '#81A1C1', | |
'aeroway_color': '#B48EAD', | |
'man_made_color': '#D08770' | |
}, | |
'テーマ4': { | |
'bg_color': '#F0ECE3', | |
'greenery_color': '#679436', | |
'water_color': '#5B84B1', | |
'building_colors': ['#A44A3F', '#E7BB41', '#C88D29', '#B4B8AB', '#E4B363'], | |
'major_road_color': '#4F6D7A', | |
'medium_road_color': '#C0D6DF', | |
'minor_road_color': '#EAEAEA', | |
'railway_color': '#8D230F', | |
'amenity_color': '#FFD700', | |
'waterway_color': '#1E90FF', | |
'aeroway_color': '#8A2BE2', | |
'man_made_color': '#FF69B4' | |
}, | |
'テーマ5': { | |
'bg_color': '#FFFFFF', | |
'greenery_color': '#228B22', | |
'water_color': '#4682B4', | |
'building_colors': ['#708090', '#2F4F4F', '#696969', '#A9A9A9', '#778899'], | |
'major_road_color': '#000000', | |
'medium_road_color': '#696969', | |
'minor_road_color': '#A9A9A9', | |
'railway_color': '#FF0000', | |
'amenity_color': '#FFD700', | |
'waterway_color': '#1E90FF', | |
'aeroway_color': '#8A2BE2', | |
'man_made_color': '#FF69B4' | |
}, | |
'カスタム': None | |
} | |
theme_name = st.selectbox("カラーテーマを選択", list(color_themes.keys())) | |
if theme_name != 'カスタム': | |
colors = color_themes[theme_name] | |
else: | |
# 色設定 | |
with st.expander("カスタムカラーパレット"): | |
bg_color = st.color_picker("背景色を選択", "#FAF3E0") | |
greenery_color = st.color_picker("緑地の色を選択", "#80C341") | |
water_color = st.color_picker("水域の色を選択", "#45A6FF") | |
building_colors = [ | |
st.color_picker(f"建物の色 {i+1}", default_color) | |
for i, default_color in enumerate(['#FF6F61', '#FFAB73', '#FFA07A', '#FFD700', '#F08080']) | |
] | |
major_road_color = st.color_picker("主要道路の色を選択", "#FF6347") | |
medium_road_color = st.color_picker("中程度の道路の色を選択", "#FF4500") | |
minor_road_color = st.color_picker("小規模な道路の色を選択", "#D2691E") | |
railway_color = st.color_picker("鉄道の色を選択", "#8B008B") | |
amenity_color = st.color_picker("アメニティの色を選択", "#FFD700") | |
waterway_color = st.color_picker("水路の色を選択", "#1E90FF") | |
aeroway_color = st.color_picker("航空施設の色を選択", "#8A2BE2") | |
man_made_color = st.color_picker("人工構造物の色を選択", "#FF69B4") | |
colors = { | |
'bg_color': bg_color, | |
'greenery_color': greenery_color, | |
'water_color': water_color, | |
'building_colors': building_colors, | |
'major_road_color': major_road_color, | |
'medium_road_color': medium_road_color, | |
'minor_road_color': minor_road_color, | |
'railway_color': railway_color, | |
'amenity_color': amenity_color, | |
'waterway_color': waterway_color, | |
'aeroway_color': aeroway_color, | |
'man_made_color': man_made_color | |
} | |
# 画像設定 | |
with st.expander("画像設定(オプション)"): | |
dpi = st.slider("画像のDPI(解像度)を選択", 72, 600, 300) | |
width = st.slider("画像の幅(インチ)を選択", 5, 30, 20) | |
height = st.slider("画像の高さ(インチ)を選択", 5, 30, 20) | |
# マップ生成ボタン | |
if st.button("マップを生成"): | |
with st.spinner('マップを生成中...'): | |
if scale_type == "ローカル(距離指定)": | |
map_image = create_artistic_map( | |
lat=lat, lon=lon, distance=distance, dpi=dpi, width=width, height=height, colors=colors | |
) | |
elif scale_type == "行政区域" and polygon is not None: | |
map_image = create_artistic_map( | |
polygon=polygon, dpi=dpi, width=width, height=height, colors=colors | |
) | |
else: | |
st.error("マップを生成するための情報が不足しています。") | |
map_image = None | |
if map_image: | |
st.image(map_image, caption="生成されたアートマップ", use_column_width=True) | |