initial setting at 20-Aug-2025
This commit is contained in:
634
rog/views_apis/api_routes.py
Executable file
634
rog/views_apis/api_routes.py
Executable file
@ -0,0 +1,634 @@
|
||||
# 既存のインポート部分に追加
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rog.models import NewEvent2, Entry, Location, GpsLog
|
||||
import logging
|
||||
from django.db.models import F, Q
|
||||
from django.conf import settings
|
||||
import os
|
||||
from urllib.parse import urljoin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
"""
|
||||
解説
|
||||
この実装では以下の処理を行っています:
|
||||
|
||||
1.イベントコードとクラス名のパラメータを受け取ります。省略可能なパラメータとして limit(表示するトップ選手の数)も受け取ります。
|
||||
2.パラメータが不足している場合はエラーを返します。
|
||||
3.指定されたイベントの存在を確認します。
|
||||
4.指定されたクラスに所属する、ゴール済みのチームをスコア降順で取得します。
|
||||
5.上位 limit 件(デフォルトは3件)のチームについて、詳細なルート情報を収集します:
|
||||
- チームの基本情報(名前、ゼッケン番号、クラス、順位、スコア)
|
||||
- スタート情報とゴール情報
|
||||
- ウェイポイントとチェックポイントのデータ
|
||||
- ルートの統計情報(総距離、ウェイポイント数、チェックポイント数)
|
||||
6.各チームに特徴的な色を割り当て(1位: 金色、2位: 銀色、3位: 銅色)、ルートを区別しやすくします。
|
||||
7.全チームの情報と、GeoJSON形式のデータを含む応答を返します。
|
||||
|
||||
実装のポイント:
|
||||
|
||||
- /getAllRoutes とは異なり、上位チームのみを対象としています。
|
||||
- 上位チームは詳細なルートポイント情報も含めることで、より詳しい分析が可能です。
|
||||
- 各チームのルートを視覚的に区別しやすくするための色情報を追加しています。
|
||||
- チェックポイントもGeoJSON内のポイントフィーチャーとして追加し、各チームがどのチェックポイントを通過したかを可視化しやすくしています。
|
||||
|
||||
このエンドポイントは、結果分析や表彰式でのプレゼンテーション、上位選手のルート戦略の研究などに役立ちます。
|
||||
"""
|
||||
|
||||
from math import sin, cos, sqrt, atan2, radians
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@api_view(['GET'])
|
||||
def top_users_routes(request):
|
||||
"""
|
||||
指定クラスのトップ選手のルート情報を取得
|
||||
|
||||
パラメータ:
|
||||
- event_code: イベントコード
|
||||
- class_name: クラス名
|
||||
- limit: 取得するトップ選手の数(省略時は3)
|
||||
"""
|
||||
logger.info("top_users_routes called")
|
||||
|
||||
# リクエストからパラメータを取得
|
||||
event_code = request.query_params.get('event_code')
|
||||
class_name = request.query_params.get('class_name')
|
||||
|
||||
# クエリパラメータの別名対応(互換性のため)
|
||||
if not event_code:
|
||||
event_code = request.query_params.get('event')
|
||||
|
||||
# 省略可能なパラメータ
|
||||
try:
|
||||
limit = int(request.query_params.get('limit', 3))
|
||||
except ValueError:
|
||||
limit = 3
|
||||
|
||||
logger.debug(f"Parameters: event_code={event_code}, class_name={class_name}, limit={limit}")
|
||||
|
||||
# パラメータ検証
|
||||
if not all([event_code, class_name]):
|
||||
logger.warning("Missing required parameters")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "イベントコードとクラス名が必要です"
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
# イベントの存在確認
|
||||
event = NewEvent2.objects.filter(event_name=event_code).first()
|
||||
if not event:
|
||||
logger.warning(f"Event not found: {event_code}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "指定されたイベントが見つかりません"
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# ゴール済みで指定クラスに属する上位チームを取得
|
||||
top_entries = Entry.objects.filter(
|
||||
event=event,
|
||||
class_name=class_name,
|
||||
goal_info__isnull=False # ゴール済みのチームのみ
|
||||
).order_by('-goal_info__score')[:limit] # スコア降順で上位limit件
|
||||
|
||||
if not top_entries.exists():
|
||||
logger.warning(f"No teams with goal info found for event: {event_code}, class: {class_name}")
|
||||
return Response({
|
||||
"status": "WARNING",
|
||||
"message": "ゴール済みのチームが見つかりません",
|
||||
"teams": [],
|
||||
"total_teams": 0
|
||||
})
|
||||
|
||||
# トップチームの情報とルートを取得
|
||||
teams_data = []
|
||||
geo_json_features = []
|
||||
|
||||
for entry in top_entries:
|
||||
try:
|
||||
# ウェイポイントデータを取得(時間順)
|
||||
waypoints = Waypoint.objects.filter(
|
||||
entry=entry
|
||||
).order_by('recorded_at')
|
||||
|
||||
# チェックポイント通過情報を取得(時間順)
|
||||
checkpoints = GpsLog.objects.filter(
|
||||
entry=entry
|
||||
).order_by('checkin_time')
|
||||
|
||||
# スタート情報を取得
|
||||
start_info = None
|
||||
if hasattr(entry, 'start_info'):
|
||||
start_info = {
|
||||
"start_time": entry.start_info.start_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
}
|
||||
|
||||
# ゴール情報を取得
|
||||
goal_info = None
|
||||
if hasattr(entry, 'goal_info'):
|
||||
goal_info = {
|
||||
"goal_time": entry.goal_info.goal_time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"score": entry.goal_info.score
|
||||
}
|
||||
|
||||
# ウェイポイントとチェックポイントを処理
|
||||
route_points = []
|
||||
|
||||
# ウェイポイントを処理
|
||||
for wp in waypoints:
|
||||
point = {
|
||||
"latitude": wp.latitude,
|
||||
"longitude": wp.longitude,
|
||||
"timestamp": wp.recorded_at.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"type": "waypoint"
|
||||
}
|
||||
|
||||
if wp.altitude is not None:
|
||||
point["altitude"] = wp.altitude
|
||||
if wp.accuracy is not None:
|
||||
point["accuracy"] = wp.accuracy
|
||||
if wp.speed is not None:
|
||||
point["speed"] = wp.speed
|
||||
|
||||
route_points.append(point)
|
||||
|
||||
# チェックポイントの座標を追加(イベントのチェックポイント定義がある場合)
|
||||
checkpoint_data = []
|
||||
for cp in checkpoints:
|
||||
cp_data = {
|
||||
"cp_number": cp.cp_number,
|
||||
"checkin_time": cp.checkin_time.strftime("%Y-%m-%d %H:%M:%S") if cp.checkin_time else None,
|
||||
"is_service_checked": getattr(cp, 'is_service_checked', False)
|
||||
}
|
||||
|
||||
# チェックポイントの座標情報を取得
|
||||
try:
|
||||
event_cp = Location.objects.filter(
|
||||
event=event,
|
||||
cp_number=cp.cp_number
|
||||
).first()
|
||||
|
||||
if event_cp and event_cp.latitude and event_cp.longitude:
|
||||
cp_data["latitude"] = event_cp.latitude
|
||||
cp_data["longitude"] = event_cp.longitude
|
||||
cp_data["cp_name"] = event_cp.cp_name
|
||||
cp_data["cp_point"] = event_cp.cp_point
|
||||
|
||||
# ルートポイントとしても追加
|
||||
route_points.append({
|
||||
"latitude": event_cp.latitude,
|
||||
"longitude": event_cp.longitude,
|
||||
"timestamp": cp.checkin_time.strftime("%Y-%m-%d %H:%M:%S") if cp.checkin_time else None,
|
||||
"type": "checkpoint",
|
||||
"cp_number": cp.cp_number
|
||||
})
|
||||
except:
|
||||
# Locationモデルが存在しないか、座標が設定されていない場合
|
||||
pass
|
||||
|
||||
checkpoint_data.append(cp_data)
|
||||
|
||||
# ルートの統計情報を計算
|
||||
total_distance = 0
|
||||
if len(route_points) >= 2:
|
||||
def calculate_distance(lat1, lon1, lat2, lon2):
|
||||
# 2点間の距離をハバーサイン公式で計算(km単位)
|
||||
R = 6371.0 # 地球の半径(km)
|
||||
|
||||
lat1_rad = radians(lat1)
|
||||
lon1_rad = radians(lon1)
|
||||
lat2_rad = radians(lat2)
|
||||
lon2_rad = radians(lon2)
|
||||
|
||||
dlon = lon2_rad - lon1_rad
|
||||
dlat = lat2_rad - lat1_rad
|
||||
|
||||
a = sin(dlat / 2)**2 + cos(lat1_rad) * cos(lat2_rad) * sin(dlon / 2)**2
|
||||
c = 2 * atan2(sqrt(a), sqrt(1 - a))
|
||||
|
||||
distance = R * c
|
||||
return distance
|
||||
|
||||
for i in range(len(route_points) - 1):
|
||||
p1 = route_points[i]
|
||||
p2 = route_points[i + 1]
|
||||
|
||||
if 'latitude' in p1 and 'longitude' in p1 and 'latitude' in p2 and 'longitude' in p2:
|
||||
distance = calculate_distance(
|
||||
p1['latitude'], p1['longitude'],
|
||||
p2['latitude'], p2['longitude']
|
||||
)
|
||||
total_distance += distance
|
||||
|
||||
# チームデータを追加
|
||||
team_data = {
|
||||
"rank": list(top_entries).index(entry) + 1, # 順位
|
||||
"team_name": entry.team_name,
|
||||
"zekken_number": entry.zekken_number,
|
||||
"class_name": entry.class_name,
|
||||
"score": goal_info["score"] if goal_info else None,
|
||||
"start_info": start_info,
|
||||
"goal_info": goal_info,
|
||||
"route_statistics": {
|
||||
"total_distance": round(total_distance, 2),
|
||||
"total_waypoints": waypoints.count(),
|
||||
"total_checkpoints": checkpoints.count()
|
||||
},
|
||||
"checkpoints": checkpoint_data,
|
||||
"route_points": route_points # トップチームなので詳細なルートデータも含める
|
||||
}
|
||||
|
||||
teams_data.append(team_data)
|
||||
|
||||
# GeoJSONフィーチャーを追加
|
||||
if route_points:
|
||||
# 特徴的な色をチームに割り当て(上位3チームの場合、金・銀・銅のイメージ)
|
||||
colors = ["#FFD700", "#C0C0C0", "#CD7F32"] # Gold, Silver, Bronze
|
||||
rank = list(top_entries).index(entry)
|
||||
color = colors[rank] if rank < len(colors) else f"#{(rank * 50) % 256:02x}{100:02x}{150:02x}"
|
||||
|
||||
geo_json_features.append({
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[point["longitude"], point["latitude"]]
|
||||
for point in route_points
|
||||
if "latitude" in point and "longitude" in point
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"team_name": entry.team_name,
|
||||
"zekken_number": entry.zekken_number,
|
||||
"class_name": entry.class_name,
|
||||
"rank": list(top_entries).index(entry) + 1,
|
||||
"score": goal_info["score"] if goal_info else None,
|
||||
"total_distance": round(total_distance, 2),
|
||||
"color": color,
|
||||
"waypoint_count": waypoints.count(),
|
||||
"checkpoint_count": checkpoints.count()
|
||||
}
|
||||
})
|
||||
|
||||
# チェックポイントをポイントフィーチャーとして追加
|
||||
for cp in checkpoint_data:
|
||||
if 'latitude' in cp and 'longitude' in cp:
|
||||
geo_json_features.append({
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [cp["longitude"], cp["latitude"]]
|
||||
},
|
||||
"properties": {
|
||||
"team_name": entry.team_name,
|
||||
"zekken_number": entry.zekken_number,
|
||||
"type": "checkpoint",
|
||||
"cp_number": cp["cp_number"],
|
||||
"cp_name": cp.get("cp_name", cp["cp_number"]),
|
||||
"color": color,
|
||||
"rank": list(top_entries).index(entry) + 1
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing route for team {entry.team_name}: {str(e)}")
|
||||
# エラーが発生したチームはスキップ
|
||||
continue
|
||||
|
||||
# 対象クラスの全チーム数(ゴール済み)を取得
|
||||
total_teams_in_class = Entry.objects.filter(
|
||||
event=event,
|
||||
class_name=class_name,
|
||||
goal_info__isnull=False
|
||||
).count()
|
||||
|
||||
# GeoJSON形式のデータを作成
|
||||
geo_json = {
|
||||
"type": "FeatureCollection",
|
||||
"features": geo_json_features
|
||||
}
|
||||
|
||||
return Response({
|
||||
"status": "OK",
|
||||
"event_code": event_code,
|
||||
"class_name": class_name,
|
||||
"top_limit": limit,
|
||||
"total_teams_in_class": total_teams_in_class,
|
||||
"total_top_teams": len(teams_data),
|
||||
"teams": teams_data,
|
||||
"geo_json": geo_json
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in top_users_routes: {str(e)}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "サーバーエラーが発生しました"
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
pip install folium selenium webdriver-manager Pillow
|
||||
"""
|
||||
|
||||
# 既存のインポート部分に追加
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rog.models import NewEvent2, Entry, Waypoint, GpsLog
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
import folium
|
||||
from folium.plugins import MarkerCluster
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.chrome.service import Service
|
||||
from webdriver_manager.chrome import ChromeDriverManager
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.files.base import ContentFile
|
||||
from PIL import Image
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from django.utils import timezone
|
||||
|
||||
"""
|
||||
解説
|
||||
この実装では以下の処理を行っています:
|
||||
|
||||
1.イベントコードとゼッケン番号のパラメータを受け取ります
|
||||
2.パラメータが不足している場合はエラーを返します
|
||||
3.指定されたイベントとチームの存在を確認します
|
||||
4.チームのウェイポイントとチェックポイントデータを取得します
|
||||
5.Foliumライブラリを使用して地図を生成し、以下の情報を描画します:
|
||||
- ルート(青い線)
|
||||
- スタート地点(緑のマーカー)
|
||||
- 現在地点/ゴール地点(赤のマーカー)
|
||||
- チェックポイント(緑のフラグ:通過済み、グレーのフラグ:未通過)
|
||||
- チーム情報(名前、ゼッケン番号、クラス、スタート時間、ゴール時間、スコア)
|
||||
6.生成した地図をHTMLとして一時保存します
|
||||
7.Seleniumを使用して、HTMLのスクリーンショットを撮影しPNG画像を生成します
|
||||
8.画像をメディアディレクトリに保存し、URLを生成します
|
||||
9.画像のURLを含むレスポンスを返します
|
||||
|
||||
実装のポイント:
|
||||
|
||||
- Foliumライブラリを使用することで、OpenStreetMapをベースにした高品質な地図を生成できます
|
||||
- Seleniumでのスクリーンショット撮影により、インタラクティブな地図を静的画像として保存できます
|
||||
- チェックポイントの通過状況に応じてマーカーの色を変えることで、視覚的な情報を提供します
|
||||
- チーム情報を地図上に表示することで、画像だけで必要な情報が分かるようにしています
|
||||
|
||||
注意点:
|
||||
|
||||
- この実装にはSeleniumとChromeドライバーが必要です。サーバー環境によっては追加の設定が必要になる場合があります
|
||||
- 画像生成は比較的重い処理なので、リクエストが多い場合はキャッシュ機能の実装を検討するとよいでしょう
|
||||
|
||||
このエンドポイントにより、チームの移動ルートを可視化した画像を生成し、ウェブページに埋め込んだり、
|
||||
SNSで共有したりすることができます。
|
||||
"""
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@api_view(['GET'])
|
||||
def generate_route_image(request):
|
||||
"""
|
||||
チームのルートを可視化した画像を生成
|
||||
|
||||
パラメータ:
|
||||
- event_code: イベントコード
|
||||
- zekken_number: ゼッケン番号
|
||||
"""
|
||||
logger.info("generate_route_image called")
|
||||
|
||||
# リクエストからパラメータを取得
|
||||
event_code = request.query_params.get('event_code')
|
||||
zekken_number = request.query_params.get('zekken_number')
|
||||
|
||||
# 別名のパラメータ対応
|
||||
if not event_code:
|
||||
event_code = request.query_params.get('event')
|
||||
if not zekken_number:
|
||||
zekken_number = request.query_params.get('zekken')
|
||||
|
||||
logger.debug(f"Parameters: event_code={event_code}, zekken_number={zekken_number}")
|
||||
|
||||
# パラメータ検証
|
||||
if not all([event_code, zekken_number]):
|
||||
logger.warning("Missing required parameters")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "イベントコードとゼッケン番号が必要です"
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
# イベントの存在確認
|
||||
event = NewEvent2.objects.filter(event_name=event_code).first()
|
||||
if not event:
|
||||
logger.warning(f"Event not found: {event_code}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "指定されたイベントが見つかりません"
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# チームの存在確認
|
||||
entry = Entry.objects.filter(
|
||||
event=event,
|
||||
zekken_number=zekken_number
|
||||
).first()
|
||||
|
||||
if not entry:
|
||||
logger.warning(f"Team with zekken number {zekken_number} not found in event: {event_code}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "指定されたゼッケン番号のチームが見つかりません"
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# ウェイポイントデータを取得(時間順)
|
||||
waypoints = Waypoint.objects.filter(
|
||||
entry=entry
|
||||
).order_by('recorded_at')
|
||||
|
||||
if not waypoints.exists():
|
||||
logger.warning(f"No waypoints found for team: {entry.team_name}")
|
||||
return Response({
|
||||
"status": "WARNING",
|
||||
"message": "このチームのウェイポイントデータがありません"
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
# チェックポイント通過情報を取得(時間順)
|
||||
checkpoints = GpsLog.objects.filter(
|
||||
entry=entry
|
||||
).order_by('checkin_time')
|
||||
|
||||
# チェックポイント情報の取得(座標)
|
||||
cp_locations = {}
|
||||
try:
|
||||
event_cps = Location.objects.filter(event=event)
|
||||
|
||||
for cp in event_cps:
|
||||
if cp.latitude is not None and cp.longitude is not None:
|
||||
cp_locations[cp.cp_number] = {
|
||||
'lat': cp.latitude,
|
||||
'lon': cp.longitude,
|
||||
'name': cp.cp_name,
|
||||
'point': cp.cp_point
|
||||
}
|
||||
except:
|
||||
# Locationモデルが存在しない場合
|
||||
pass
|
||||
|
||||
# チームが通過したチェックポイント番号のリスト
|
||||
visited_cps = set([cp.cp_number for cp in checkpoints])
|
||||
|
||||
# 地図の中心座標を計算(全ウェイポイントの中央値)
|
||||
lats = [wp.latitude for wp in waypoints]
|
||||
lons = [wp.longitude for wp in waypoints]
|
||||
center_lat = sum(lats) / len(lats)
|
||||
center_lon = sum(lons) / len(lons)
|
||||
|
||||
# スタート地点と終了地点
|
||||
start_point = (waypoints.first().latitude, waypoints.first().longitude)
|
||||
end_point = (waypoints.last().latitude, waypoints.last().longitude)
|
||||
|
||||
# Folium地図を作成
|
||||
m = folium.Map(location=[center_lat, center_lon], zoom_start=13)
|
||||
|
||||
# ルートを描画
|
||||
route_points = [(wp.latitude, wp.longitude) for wp in waypoints]
|
||||
folium.PolyLine(
|
||||
route_points,
|
||||
color='blue',
|
||||
weight=3,
|
||||
opacity=0.7
|
||||
).add_to(m)
|
||||
|
||||
# スタート地点をマーク
|
||||
folium.Marker(
|
||||
start_point,
|
||||
popup="スタート",
|
||||
icon=folium.Icon(color='green', icon='play', prefix='fa')
|
||||
).add_to(m)
|
||||
|
||||
# 終了地点をマーク
|
||||
folium.Marker(
|
||||
end_point,
|
||||
popup="現在位置 / ゴール",
|
||||
icon=folium.Icon(color='red', icon='stop', prefix='fa')
|
||||
).add_to(m)
|
||||
|
||||
# チェックポイントを描画
|
||||
for cp_number, cp_info in cp_locations.items():
|
||||
visited = cp_number in visited_cps
|
||||
color = 'green' if visited else 'gray'
|
||||
|
||||
folium.Marker(
|
||||
[cp_info['lat'], cp_info['lon']],
|
||||
popup=f"{cp_info['name']} ({cp_info['point']}点) - {'通過済' if visited else '未通過'}",
|
||||
icon=folium.Icon(color=color, icon='flag', prefix='fa')
|
||||
).add_to(m)
|
||||
|
||||
# チーム情報を追加
|
||||
team_info = f"""
|
||||
<div style="font-size: 12px; font-family: Arial;">
|
||||
<h3>チーム情報</h3>
|
||||
<p>チーム名: {entry.team_name}</p>
|
||||
<p>ゼッケン番号: {entry.zekken_number}</p>
|
||||
<p>クラス: {entry.class_name}</p>
|
||||
"""
|
||||
|
||||
if hasattr(entry, 'start_info'):
|
||||
team_info += f"<p>スタート時間: {entry.start_info.start_time.strftime('%Y-%m-%d %H:%M:%S')}</p>"
|
||||
|
||||
if hasattr(entry, 'goal_info'):
|
||||
team_info += f"""
|
||||
<p>ゴール時間: {entry.goal_info.goal_time.strftime('%Y-%m-%d %H:%M:%S')}</p>
|
||||
<p>スコア: {entry.goal_info.score}点</p>
|
||||
"""
|
||||
|
||||
team_info += "</div>"
|
||||
|
||||
# チーム情報をマップに追加
|
||||
folium.Element(team_info).add_to(m)
|
||||
|
||||
# 一時的にHTMLファイルを保存
|
||||
timestamp = timezone.now().strftime("%Y%m%d%H%M%S")
|
||||
html_file = f"route_map_{event_code}_{zekken_number}_{timestamp}.html"
|
||||
|
||||
# 一時ディレクトリを作成
|
||||
tmp_dir = os.path.join(settings.MEDIA_ROOT, 'tmp', 'route_maps')
|
||||
os.makedirs(tmp_dir, exist_ok=True)
|
||||
html_path = os.path.join(tmp_dir, html_file)
|
||||
|
||||
m.save(html_path)
|
||||
|
||||
# HTMLをPNGに変換
|
||||
png_file = f"route_map_{event_code}_{zekken_number}_{timestamp}.png"
|
||||
png_path = os.path.join(tmp_dir, png_file)
|
||||
|
||||
# Seleniumを使用してスクリーンショットを撮影
|
||||
try:
|
||||
# Chromeオプションを設定
|
||||
options = webdriver.ChromeOptions()
|
||||
options.add_argument('--headless')
|
||||
options.add_argument('--no-sandbox')
|
||||
options.add_argument('--disable-dev-shm-usage')
|
||||
options.add_argument('--disable-gpu')
|
||||
|
||||
# ウィンドウサイズを設定
|
||||
options.add_argument('--window-size=1200,900')
|
||||
|
||||
# WebDriverを初期化
|
||||
service = Service(ChromeDriverManager().install())
|
||||
driver = webdriver.Chrome(service=service, options=options)
|
||||
|
||||
# HTMLファイルを開く
|
||||
driver.get(f"file://{html_path}")
|
||||
|
||||
# 地図の読み込みを待機
|
||||
time.sleep(2)
|
||||
|
||||
# スクリーンショットを撮影
|
||||
driver.save_screenshot(png_path)
|
||||
driver.quit()
|
||||
|
||||
# メディアディレクトリに移動
|
||||
final_path = os.path.join('route_maps', png_file)
|
||||
with open(png_path, 'rb') as f:
|
||||
default_storage.save(final_path, ContentFile(f.read()))
|
||||
|
||||
# 画像のURLを生成
|
||||
image_url = settings.MEDIA_URL + final_path
|
||||
|
||||
# 一時ファイルを削除
|
||||
if os.path.exists(html_path):
|
||||
os.remove(html_path)
|
||||
if os.path.exists(png_path):
|
||||
os.remove(png_path)
|
||||
|
||||
return Response({
|
||||
"status": "OK",
|
||||
"team_name": entry.team_name,
|
||||
"event_code": event_code,
|
||||
"zekken_number": zekken_number,
|
||||
"image_url": image_url
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating screenshot: {str(e)}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "地図の画像生成に失敗しました"
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in generate_route_image: {str(e)}")
|
||||
return Response({
|
||||
"status": "ERROR",
|
||||
"message": "サーバーエラーが発生しました"
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
Reference in New Issue
Block a user