📍
住所から緯度経度を取得し、地図上に可視化するアプリの最小構成検討
第一に、地図上に表示したいポイントの住所を「Location」というカラムに記載したCSVファイルを用意する。
今回は美味しいアイスでお馴染み、シャトレーゼの工場の住所をお借りした。
# | Factory_name | Location |
---|---|---|
1 | 白州工場 | 山梨県北杜市白州町白須大原8383−1 |
2 | 豊富工場 | 山梨県中央市高部1111-1 |
CSVには住所しか含まれておらず、地図上に可視化するために使用する緯度経度の情報を付与したい。
そこで、今回は下記のコードで地理座標を付与する。
(今回はMapboxのGeocoding APIを使用したが、ここは任意のAPIを使用すれば良い。)
import pandas as pd
import requests
def get_lat_lng_from_address(address, access_token):
"""
Mapbox Geocoding APIを使用して、与えられた住所の緯度経度を取得する。
"""
endpoint_url = "https://api.mapbox.com/geocoding/v5/mapbox.places/"
params = {
'access_token': access_token,
'limit': 1, # 最も関連性の高い結果のみを返す
'query': address
}
response = requests.get(f"{endpoint_url}{address}.json", params=params)
if response.status_code == 200:
results = response.json()['features']
if results:
location = results[0]['geometry']['coordinates'] # 経度、緯度の順番で返される
return location[1], location[0] # 緯度、経度の順番に変更して返す
return None, None
def add_lat_lng_to_csv(input_csv_path, output_csv_path, access_token):
"""
CSVファイルを読み込み、各行の住所から緯度経度を取得して新しいカラムに追加し、
結果を新しいCSVファイルに保存する。
"""
df = pd.read_csv(input_csv_path)
lat_lng = df['Location'].apply(lambda x: get_lat_lng_from_address(x, access_token))
df['緯度'], df['経度'] = zip(*lat_lng)
df.to_csv(output_csv_path, index=False)
# 使用例
ACCESS_TOKEN = 'APIkey'
input_csv_path = 'factory.csv' # 入力CSVファイルのパス
output_csv_path = 'output.csv' # 出力CSVファイルのパス
add_lat_lng_to_csv(input_csv_path, output_csv_path, ACCESS_TOKEN)
次に、可視化部に移る。
まず、全体のディレクトリ構造は以下の通り。
backend
L main.py
L output.csv
frontend
L index.html
バックエンドのmain.pyはFastAPIを使用した。
import requests
from fastapi import FastAPI
from fastapi.responses import HTMLResponse, JSONResponse
import pandas as pd
from fastapi.middleware.cors import CORSMiddleware
def get_weather_info(latitude, longitude, api_key):
url = f"https://api.tomorrow.io/v4/timelines"
querystring = {
"location": f"{latitude},{longitude}",
"fields": ["temperature", "weatherCode"],
"units": "metric",
"timesteps": "1h",
"apikey": api_key
}
response = requests.request("GET", url, params=querystring)
if response.status_code == 200:
data = response.json()
# 天気情報の取得、ここでは最初の時間のデータを取得しています。
weather_info = data['data']['timelines'][0]['intervals'][0]['values']
return weather_info
else:
print(response.status_code)
return None
# APIキーを設定
API_KEY = 'Tomorrow.io API key'
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 安全な設定にするためには、"*"の代わりに具体的なオリジンを指定してください。
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/", response_class=HTMLResponse)
async def read_root():
# CSVファイルから緯度経度データを読み込む
df = pd.read_csv('output.csv')
# HTMLの内容を構築
html_content = "<h1>工場の天気情報</h1>"
for index, row in df.iterrows():
weather_info = get_weather_info(row['緯度'], row['経度'], API_KEY)
html_content += f"<p>{row['Factory_name']}: {weather_info}</p>"
return html_content
@app.get("/weather")
async def get_weather():
df = pd.read_csv('output.csv') # CSVファイルからデータを読み込む
# 緯度、経度、工場名、天気情報を含むJSONを生成
data = []
for index, row in df.iterrows():
temp = get_weather_info(row['緯度'], row['経度'], API_KEY)
print("temp:",temp)
data.append({
"latitude": row["緯度"],
"longitude": row["経度"],
"factoryName": row["Factory_name"],
"weather": "sunny"
})
print("Response : ",data)
return JSONResponse(content=data)
backendのHTMLファイルは下記。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>工場の位置と天気情報</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v2.3.1/mapbox-gl.css" rel="stylesheet">
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script src="https://api.mapbox.com/mapbox-gl-js/v2.3.1/mapbox-gl.js"></script>
<script>
mapboxgl.accessToken = 'Mapbox token';
var map = new mapboxgl.Map({
container: 'map', // container ID
style: 'mapbox://styles/mapbox/streets-v11', // style URL
center: [139.6917, 35.6895], // 初期位置を日本に設定
zoom: 5 // 初期ズームレベル
});
map.on('load', function () {
// FastAPIから天気情報を取得し、地図上にマーカーを追加
fetch('http://localhost:8000/weather') // FastAPIのエンドポイントに合わせて変更
.then(response => response.json())
.then(data => {
data.forEach(item => {
// マーカーを作成して地図上に追加
new mapboxgl.Marker()
.setLngLat([item.longitude, item.latitude])
.setPopup(new mapboxgl.Popup({ offset: 25 }) // ポップアップの追加
.setHTML(`<h3>${item.factoryName}</h3><p>${item.weather}</p>`))
.addTo(map);
});
})
.catch(error => console.log('Error:', error));
});
</script>
</body>
</html>
#一部天気を取得しようとして諦めた痕跡が残っているが、これは成功したら更新する予定。
上記の配置が完了したら、下記のコマンドでFastAPIを起動。
uvicorn backend.main:app --reload
サーバ起動後、backend/index.htmlを起動すると、地図が表示される。
Discussion