前回データベースから周辺店舗の一覧を取得できるようになったけど、一覧表示のままだと、その店がどこにあるか分かりづらいね
じゃあ今回は、それを使って地図上に「ピン」を立ててみようか!
「ピン」って、googleマップで場所を検索した時に位置を教えてくれるやつだよね
そうそう
このピンを立てる機能も「folium」を使えば簡単に設置できるから試してみよう!
お願いします!
じゃあまずは、練習として検索する時に指定した座標にピンを立ててみようか
ピンを立てる方法もシンプルで、「folium.Marker([緯度, 経度], popup='ポップアップメッセージ').add_to(地図を管理している変数名)」で追加できるよ
ほうほう
ただ、ピンを立てる場合は地図を表示するプログラムより前に用意する必要があるから注意してね
folium.Marker([lat, lon], popup="検索地点", icon=folium.Icon(color="blue")).add_to(m)
追加したよ!
ピンをクリックすると、そのピンの情報が表示されるようになってるね!
バッチリだね!
ちなみに「popup="〇〇"」の部分はHTML形式で書き込むとリンクの作成とかも出来るから覚えておくと良いよ!
そうなんだ!
まだHTMLはよく分からないけど、使えるようになったら試してみる!!
じゃあ次は、周辺店舗のピンを立ててみようか!
これは変数の「restaurants」をforループで処理していけばOK!
# 「restaurants = ~」の次の行くらいに追加しよう
for res in restaurants:
folium.Marker(
location=[res['lat'], res['lon']],
tooltip=res['name'],
popup=f"{res['name']} ({res['amenity']})",
icon=folium.Icon(color="red", icon="cutlery", prefix="fa")
).add_to(m)
赤色のピンがいっぱい出てきた!!
良い感じ!
じゃあついでに、検索範囲を調整できる仕組みも追加しておこうか
おおー良いね!!
まずは検索範囲を調整できるスライダーを用意しようか
「st.form()」の部分にある「target_place」と「submitted」の間に次のプログラムを追加しよう
search_radius = st.slider("検索範囲 (km)", 0.5, 3.0, 1.0)
今回は最小範囲を「0.5」、最大範囲を「3.0」、初期値を「1.0」として設定してるけど、好みで調整してみてね!
オッケー!
あとは、変数「restaurants」に検索結果を返すプログラムの引数にスライダーの情報を追加するだけでOK!
restaurants = search_restaurants(lat, lon, radius_km=search_radius)
ちゃんとスライダー機能が使えるようになったよ!!
これで地図アプリはひとまず完成だね!
他にも利用できる地理情報には、OpenStreetMapで使われているルート検索エンジン「OSRM」や国土交通省が連携している3Dの都市モデル「PLATEAU」、「国土数値情報」とかもあるから、ぜひ活かしてみてね!
上手く位置情報を利用できたら、「ポケモンGO」みたいな位置情報を利用したゲームとかも作れるかもね!
全体のプログラム
import sqlite3
import folium
from streamlit_folium import st_folium
import streamlit as st
import pandas as pd
from geopy.geocoders import Nominatim
from geopy.distance import geodesic
# 土地名から緯度経度の取得
def get_coordinates(address):
geolocator = Nominatim(user_agent="my_geocoding_app")
geocoder_result = geolocator.geocode(address, timeout=5.0)
try:
return float(geocoder_result.latitude), float(geocoder_result.longitude)
except Exception as e:
print(f"接続エラー: {e}")
return None, None
# データベースから周辺の店を探す
def search_restaurants(lat, lon, radius_km=1.0):
try:
conn = sqlite3.connect("restaurants.db")
# 検索を速くするための大まかな絞り込み(緯度経度の差)
# 距離オブジェクトの作成
d = geodesic(kilometers=1.0)
# 各方位の地点を計算
# destination(始点, 方位角) 方位角: 0=北, 90=東, 180=南, 270=西
north = d.destination((lat, lon), 0)
south = d.destination((lat, lon), 180)
east = d.destination((lat, lon), 90)
west = d.destination((lat, lon), 270)
query = f"""
SELECT * FROM restaurants
WHERE lat BETWEEN {south.latitude} AND {north.latitude}
AND lon BETWEEN {west.longitude} AND {east.longitude}
"""
df = pd.read_sql_query(query, conn)
conn.close()
# 正確な距離でフィルタリング
results = []
for _, row in df.iterrows():
dist = geodesic((lat, lon), (row['lat'], row['lon'])).km
if dist <= radius_km:
res_dict = row.to_dict()
res_dict['distance_km'] = dist
results.append(res_dict)
return sorted(results, key=lambda x: x['distance_km'])
except Exception as e:
st.error(f"データベースエラー: {e}")
return []
# 1. st.form を使って、入力中の勝手な更新を防ぐ
with st.form("search_form"):
target_place = st.text_input("調べたい場所を入力してください", "渋谷駅")
search_radius = st.slider("検索範囲 (km)", 0.5, 3.0, 1.0)
submitted = st.form_submit_button("検索する")
# 2. ボタンが押された時に検索を実行し、結果を session_state に保存する
if submitted:
if target_place:
with st.spinner("検索中..."):
lat, lon = get_coordinates(target_place)
if lat and lon:
m = folium.Map(location=[lat, lon], zoom_start=15)
folium.Marker([lat, lon], popup="検索地点", icon=folium.Icon(color="blue")).add_to(m)
restaurants = search_restaurants(lat, lon, radius_km=search_radius)
for res in restaurants:
folium.Marker(
location=[res['lat'], res['lon']],
tooltip=res['name'],
popup=f"{res['name']} ({res['amenity']})",
icon=folium.Icon(color="red", icon="cutlery", prefix="fa")
).add_to(m)
st_folium(m, width=700, height=500, returned_objects=[])
st.write(f"「{target_place}」の周辺に {len(restaurants)} 件の飲食店が見つかりました。")
# 結果をテーブルで表示
if restaurants:
df_results = pd.DataFrame(restaurants)
st.dataframe(df_results[['name', 'amenity', 'distance_km']], use_container_width=True)
else:
st.error("指定された場所が見つかりませんでした。")
else:
st.error("場所を入力してください。")
