initial setting at 20-Aug-2025

This commit is contained in:
2025-08-20 19:15:19 +09:00
parent eab529bd3b
commit 1ba305641e
149 changed files with 170449 additions and 1802 deletions

546
rog/views_apis/api_simulator.py Executable file
View File

@ -0,0 +1,546 @@
# 既存のインポート部分に追加
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from rog.models import NewEvent2
import logging
import networkx as nx
import numpy as np
import math
from haversine import haversine
import folium
import json
import os
from django.conf import settings
from django.utils import timezone
logger = logging.getLogger(__name__)
"""
解説
この実装では、ロゲイニングコースのルートシミュレーションを行う機能を提供しています:
1.データ入力とパラメータ設定:
- イベントコード、コース時間、停止時間などのパラメータを受け取ります
- 訪問すべき必須ノード(無料ノードと有料ノード)を指定できます
- 目標速度を設定し、移動時間の計算に使用します
2.グラフモデル構築:
- NetworkXライブラリを使用してグラフを構築します
- チェックポイントをノードとして、移動経路をエッジとして表現
- 各エッジには距離と所要時間の属性を付与
- Haversine公式を使用して緯度・経度から実際の距離を計算
3.ルート最適化アルゴリズム:
- 必須ノードを優先的に訪問し、残りの時間で効率良く他のノードを訪問
- ポイント/時間の比率に基づいて効率の良いチェックポイントを選択
- 制限時間内にゴールに戻ることを保証
4.結果の可視化:
- Foliumライブラリを使用して地図上にルートを可視化
- 訪問順序、ポイント情報などを地図上に表示
- 結果はHTML形式で保存され、ブラウザで確認可能
5.レスポンスデータ:
- 合計ポイント、総移動距離、総所要時間などの統計情報
- 訪問ノード数や必須ノードの完了状況
- 詳細なルート情報(各ステップの移動距離、所要時間、獲得ポイントなど)
- 生成された地図へのURL
このシミュレーターは、ロゲイニング参加者がコース計画を立てる際に役立ちます。
自分のペースや優先するチェックポイントに基づいて、最適なルートを事前に検討できます。
また、イベント主催者がコースデザインの評価や、ポイント配分の調整に活用することもできます。
"""
@api_view(['GET'])
def rogaining_simulator(request):
"""
ロゲイニングのルートシミュレーションを実行
パラメータ:
- event_code: イベントコード
- course_time: コース時間(分)
- pause_time_free: 無料CP停止時間
- pause_time_paid: 有料CP停止時間
- spare_time: 予備時間(分)
- target_velocity: 目標速度km/h
- free_node_to_visit: 訪問する無料ノード
- paid_node_to_visit: 訪問する有料ノード
"""
logger.info("rogaining_simulator called")
# リクエストからパラメータを取得
event_code = request.query_params.get('event_code')
try:
course_time = float(request.query_params.get('course_time', 180)) # デフォルト3時間
pause_time_free = float(request.query_params.get('pause_time_free', 2)) # デフォルト2分
pause_time_paid = float(request.query_params.get('pause_time_paid', 5)) # デフォルト5分
spare_time = float(request.query_params.get('spare_time', 30)) # デフォルト30分
target_velocity = float(request.query_params.get('target_velocity', 5.0)) # デフォルト5km/h
free_node_to_visit = request.query_params.get('free_node_to_visit', '')
paid_node_to_visit = request.query_params.get('paid_node_to_visit', '')
# カンマ区切りの文字列をリストに変換
free_nodes = [n.strip() for n in free_node_to_visit.split(',') if n.strip()]
paid_nodes = [n.strip() for n in paid_node_to_visit.split(',') if n.strip()]
except ValueError as e:
logger.error(f"Parameter conversion error: {str(e)}")
return Response({
"status": "ERROR",
"message": f"パラメータの変換に失敗しました: {str(e)}"
}, status=status.HTTP_400_BAD_REQUEST)
logger.debug(f"Parameters: event_code={event_code}, course_time={course_time}, "
f"pause_time_free={pause_time_free}, pause_time_paid={pause_time_paid}, "
f"spare_time={spare_time}, target_velocity={target_velocity}, "
f"free_nodes={free_nodes}, paid_nodes={paid_nodes}")
# パラメータ検証
if not event_code:
logger.warning("Missing required event_code parameter")
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)
# チェックポイント情報の取得
checkpoints = []
try:
event_cps = Location.objects.filter(event=event)
for cp in event_cps:
if not cp.latitude or not cp.longitude:
continue
checkpoints.append({
'id': cp.cp_number,
'name': cp.cp_name or cp.cp_number,
'point': cp.cp_point or 0,
'latitude': float(cp.latitude),
'longitude': float(cp.longitude),
'is_paid': cp.cp_number in paid_nodes,
'is_free': cp.cp_number in free_nodes,
'pause_time': pause_time_paid if cp.cp_number in paid_nodes else pause_time_free
})
except:
# Locationモデルが存在しない場合、ダミーデータを作成
logger.warning("Location model not found, using dummy data")
checkpoints = []
for i in range(1, 11):
cp_number = f"CP{i}"
checkpoints.append({
'id': cp_number,
'name': f"チェックポイント{i}",
'point': i * 10,
'latitude': 35.0 + i * 0.01,
'longitude': 135.0 + i * 0.01,
'is_paid': cp_number in paid_nodes,
'is_free': cp_number in free_nodes,
'pause_time': pause_time_paid if cp_number in paid_nodes else pause_time_free
})
# スタートとゴールポイントの取得
start_point = None
goal_point = None
# 設定からスタート/ゴールポイントを取得(実際のシステムに合わせて調整)
try:
# イベント設定モデルからスタート/ゴール情報を取得
from rog.models import EventSetting
event_setting = EventSetting.objects.filter(event=event).first()
if event_setting:
if all([event_setting.start_latitude, event_setting.start_longitude]):
start_point = {
'id': 'START',
'name': 'スタート地点',
'point': 0,
'latitude': float(event_setting.start_latitude),
'longitude': float(event_setting.start_longitude),
'is_paid': False,
'is_free': False,
'pause_time': 0
}
if all([event_setting.goal_latitude, event_setting.goal_longitude]):
goal_point = {
'id': 'GOAL',
'name': 'ゴール地点',
'point': 0,
'latitude': float(event_setting.goal_latitude),
'longitude': float(event_setting.goal_longitude),
'is_paid': False,
'is_free': False,
'pause_time': 0
}
except:
logger.warning("EventSetting model not found, using first CP as start/goal")
# スタート/ゴールが設定されていない場合、最初のCPをスタートとゴールに設定
if not start_point and checkpoints:
start_point = checkpoints[0].copy()
start_point['id'] = 'START'
start_point['name'] = 'スタート地点'
start_point['point'] = 0
start_point['is_paid'] = False
start_point['is_free'] = False
start_point['pause_time'] = 0
if not goal_point and checkpoints:
goal_point = checkpoints[0].copy()
goal_point['id'] = 'GOAL'
goal_point['name'] = 'ゴール地点'
goal_point['point'] = 0
goal_point['is_paid'] = False
goal_point['is_free'] = False
goal_point['pause_time'] = 0
# ルートシミュレーション実行
result = simulate_rogaining_route(
checkpoints,
start_point,
goal_point,
course_time=course_time,
spare_time=spare_time,
target_velocity=target_velocity,
free_nodes=free_nodes,
paid_nodes=paid_nodes
)
# 結果を返す
return Response({
"status": "OK",
"event_code": event_code,
"simulation_time": timezone.now().strftime("%Y-%m-%d %H:%M:%S"),
"parameters": {
"course_time": course_time,
"pause_time_free": pause_time_free,
"pause_time_paid": pause_time_paid,
"spare_time": spare_time,
"target_velocity": target_velocity,
"free_node_count": len(free_nodes),
"paid_node_count": len(paid_nodes)
},
"result": result
})
except Exception as e:
logger.error(f"Error in rogaining_simulator: {str(e)}")
return Response({
"status": "ERROR",
"message": "シミュレーション中にエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def simulate_rogaining_route(checkpoints, start_point, goal_point, course_time, spare_time, target_velocity, free_nodes, paid_nodes):
"""
ロゲイニングのルートをシミュレーション
"""
# グラフを作成
G = nx.Graph()
# ノードとエッジを追加
all_points = [start_point] + checkpoints + [goal_point]
all_points_dict = {p['id']: p for p in all_points}
# ノードを追加
for point in all_points:
G.add_node(
point['id'],
name=point['name'],
point=point['point'],
lat=point['latitude'],
lng=point['longitude'],
is_paid=point['is_paid'],
is_free=point['is_free'],
pause_time=point['pause_time']
)
# エッジを追加(全ノード間)
for i, point1 in enumerate(all_points):
for j, point2 in enumerate(all_points):
if i != j:
# 2点間の距離を計算km
dist = haversine(
(point1['latitude'], point1['longitude']),
(point2['latitude'], point2['longitude'])
)
# 時間を計算(分)
time_min = (dist / target_velocity) * 60
G.add_edge(point1['id'], point2['id'], distance=dist, time=time_min)
# 訪問必須ノードの集合
must_visit = set(free_nodes + paid_nodes)
# シミュレーション実行
available_time = course_time - spare_time # 利用可能時間(分)
# 最適化アルゴリズム - ここでは貪欲法の例
current_node = 'START'
visited = [current_node]
total_time = 0
total_points = 0
route_details = []
route_details.append({
'node': current_node,
'name': all_points_dict[current_node]['name'],
'point': 0,
'distance_from_prev': 0,
'time_from_prev': 0,
'pause_time': 0,
'cumulative_time': 0,
'cumulative_points': 0,
'lat': all_points_dict[current_node]['lat'],
'lng': all_points_dict[current_node]['lng']
})
# 訪問必須ノードを先に処理
remaining_must_visit = must_visit.copy()
while remaining_must_visit and total_time < available_time:
best_node = None
best_ratio = -1
best_time = float('inf')
for node in remaining_must_visit:
if node in visited:
continue
# 現在のノードから次のノードへの移動時間
edge_time = G.edges[current_node, node]['time']
# ノードでの停止時間
pause_time = G.nodes[node]['pause_time']
# 合計所要時間
node_time = edge_time + pause_time
# このノードの得点
node_point = G.nodes[node]['point']
# 時間あたりの得点比率(高いほど効率が良い)
ratio = node_point / node_time if node_time > 0 else 0
if ratio > best_ratio:
best_ratio = ratio
best_node = node
best_time = node_time
if best_node and total_time + best_time <= available_time:
# 移動可能な場合、訪問
edge_time = G.edges[current_node, best_node]['time']
pause_time = G.nodes[best_node]['pause_time']
distance = G.edges[current_node, best_node]['distance']
node_point = G.nodes[best_node]['point']
total_time += edge_time + pause_time
total_points += node_point
route_details.append({
'node': best_node,
'name': all_points_dict[best_node]['name'],
'point': node_point,
'distance_from_prev': distance,
'time_from_prev': edge_time,
'pause_time': pause_time,
'cumulative_time': total_time,
'cumulative_points': total_points,
'lat': all_points_dict[best_node]['lat'],
'lng': all_points_dict[best_node]['lng']
})
visited.append(best_node)
current_node = best_node
remaining_must_visit.remove(best_node)
else:
# これ以上訪問できない
break
# 残りの時間で他のノードを訪問(必須でないノードも含めて効率の良い順に)
remaining_nodes = [node for node in G.nodes if node not in visited and node != 'GOAL']
while remaining_nodes and total_time < available_time:
best_node = None
best_ratio = -1
best_time = float('inf')
for node in remaining_nodes:
# 現在のノードから次のノードへの移動時間
edge_time = G.edges[current_node, node]['time']
# ノードでの停止時間
pause_time = G.nodes[node]['pause_time']
# 合計所要時間
node_time = edge_time + pause_time
# このノードから最終的にゴールに行くのに必要な時間
to_goal_time = G.edges[node, 'GOAL']['time']
# このノードの得点
node_point = G.nodes[node]['point']
# 時間あたりの得点比率(高いほど効率が良い)
ratio = node_point / node_time if node_time > 0 else 0
# ゴールまで行ける場合のみ考慮
if total_time + node_time + to_goal_time <= available_time and ratio > best_ratio:
best_ratio = ratio
best_node = node
best_time = node_time
if best_node:
# 移動可能な場合、訪問
edge_time = G.edges[current_node, best_node]['time']
pause_time = G.nodes[best_node]['pause_time']
distance = G.edges[current_node, best_node]['distance']
node_point = G.nodes[best_node]['point']
total_time += edge_time + pause_time
total_points += node_point
route_details.append({
'node': best_node,
'name': all_points_dict[best_node]['name'],
'point': node_point,
'distance_from_prev': distance,
'time_from_prev': edge_time,
'pause_time': pause_time,
'cumulative_time': total_time,
'cumulative_points': total_points,
'lat': all_points_dict[best_node]['lat'],
'lng': all_points_dict[best_node]['lng']
})
visited.append(best_node)
current_node = best_node
remaining_nodes.remove(best_node)
else:
# これ以上訪問できない
break
# ゴールまでの移動
edge_time = G.edges[current_node, 'GOAL']['time']
distance = G.edges[current_node, 'GOAL']['distance']
total_time += edge_time
route_details.append({
'node': 'GOAL',
'name': all_points_dict['GOAL']['name'],
'point': 0,
'distance_from_prev': distance,
'time_from_prev': edge_time,
'pause_time': 0,
'cumulative_time': total_time,
'cumulative_points': total_points,
'lat': all_points_dict['GOAL']['lat'],
'lng': all_points_dict['GOAL']['lng']
})
visited.append('GOAL')
# ルートの可視化
map_file = visualize_route(route_details, event_code=start_point['name'])
# 結果まとめ
total_distance = sum(step['distance_from_prev'] for step in route_details)
# 必須ノードの訪問状況チェック
unvisited_must = must_visit - set(visited)
# マップへのURLを生成
map_url = f"/media/simulations/{os.path.basename(map_file)}" if map_file else None
result = {
'total_points': total_points,
'total_time': total_time,
'total_distance': total_distance,
'visited_nodes_count': len(visited) - 2, # スタートとゴールを除く
'must_visit_completed': len(unvisited_must) == 0,
'unvisited_must_nodes': list(unvisited_must),
'route': route_details,
'map_url': map_url
}
return result
def visualize_route(route_details, event_code='rogaining'):
"""
ルートを可視化して地図を生成
"""
try:
# ルートの中心位置を計算
lats = [step['lat'] for step in route_details]
lngs = [step['lng'] for step in route_details]
center_lat = sum(lats) / len(lats)
center_lng = sum(lngs) / len(lngs)
# 地図を作成
m = folium.Map(location=[center_lat, center_lng], zoom_start=13)
# ルートをプロット
route_points = [(step['lat'], step['lng']) for step in route_details]
folium.PolyLine(
route_points,
color='blue',
weight=3,
opacity=0.7
).add_to(m)
# スタート地点にマーカーを追加
folium.Marker(
[route_details[0]['lat'], route_details[0]['lng']],
popup='スタート',
icon=folium.Icon(color='green', icon='play')
).add_to(m)
# ゴール地点にマーカーを追加
folium.Marker(
[route_details[-1]['lat'], route_details[-1]['lng']],
popup='ゴール',
icon=folium.Icon(color='red', icon='stop')
).add_to(m)
# 各チェックポイントにマーカーを追加
for i, step in enumerate(route_details[1:-1], 1):
folium.Marker(
[step['lat'], step['lng']],
popup=f"{i}. {step['name']} ({step['point']}pts)",
icon=folium.Icon(color='blue')
).add_to(m)
# 訪問順序を表示
folium.Circle(
[step['lat'], step['lng']],
radius=10,
color='black',
fill=True,
fill_color='white',
fill_opacity=0.7,
popup=str(i)
).add_to(m)
# 保存先ディレクトリを確保
sim_dir = os.path.join(settings.MEDIA_ROOT, 'simulations')
os.makedirs(sim_dir, exist_ok=True)
# タイムスタンプを含むファイル名
timestamp = timezone.now().strftime('%Y%m%d_%H%M%S')
filename = f"route_sim_{event_code}_{timestamp}.html"
file_path = os.path.join(sim_dir, filename)
# 地図を保存
m.save(file_path)
return file_path
except Exception as e:
logger.error(f"Error visualizing route: {str(e)}")
return None