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) | |