Files
rogaining_srv/rog/views_apis/api_events.py
2025-08-20 19:15:19 +09:00

402 lines
16 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 既存のインポート部分に追加
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__)
"""
get_start_point()
解説:
この実装では以下の処理を行っています:
1.イベントコードのパラメータを受け取ります
2.パラメータが不足している場合はエラーを返します
3.指定されたイベントの存在を確認します
4. イベントのスタートポイント情報を取得します:
- 緯度・経度
- スタート地点の名前
- スタート地点の説明
5.GeoJSON形式のデータも提供します座標情報がある場合
- スタート地点をPointとして表現
- プロパティとして名前や説明も含む
このエンドポイントは、アプリでイベントのスタート地点を地図上に表示したり、
スタート地点の詳細情報を表示したりするために使用できます。
これにより、参加者はどこに集合すればよいかを正確に把握できます。
"""
@api_view(['GET'])
def get_start_point(request):
"""
イベントのスタートポイント情報を取得
パラメータ:
- event: イベントコード
"""
logger.info("get_start_point called")
# リクエストからパラメータを取得
event_code = request.query_params.get('event')
logger.debug(f"Parameters: event={event_code}")
# パラメータ検証
if not event_code:
logger.warning("Missing required event 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)
# スタートポイント情報の取得
start_point = {
"event_code": event_code,
"event_name": event.event_name
}
# 座標情報があれば追加
if event.start_latitude is not None and event.start_longitude is not None:
start_point["latitude"] = event.start_latitude
start_point["longitude"] = event.start_longitude
# 名前や説明があれば追加
if hasattr(event, 'start_name') and event.start_name:
start_point["name"] = event.start_name
if hasattr(event, 'start_description') and event.start_description:
start_point["description"] = event.start_description
# GeoJSON形式のデータも作成
geo_json = None
if event.start_latitude is not None and event.start_longitude is not None:
geo_json = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [event.start_longitude, event.start_latitude]
},
"properties": {
"name": event.start_name if hasattr(event, 'start_name') else "スタート地点",
"description": event.start_description if hasattr(event, 'start_description') else "",
"event_code": event_code
}
}
]
}
return Response({
"status": "OK",
"start_point": start_point,
"geo_json": geo_json
})
except Exception as e:
logger.error(f"Error in get_start_point: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
"""
解説
この実装では以下の処理を行っています:
1.緯度、経度、チーム名、イベントコードのパラメータを受け取ります
2.パラメータが不足している場合はエラーを返します
3.指定されたイベントとチームの存在を確認します
4.チームのウェイポイントデータを取得し、指定された座標からの距離を計算します
5.最も近いウェイポイントを特定し、以下の情報を含む分析結果を作成します:
- 最近接ポイントの詳細(位置、時間、標高、精度、速度など)
- 速度分析(前後のポイントから計算した速度と移動タイプの推測)
- 周辺ポイント(時間的・空間的に近いウェイポイント)
- 最も近いチェックポイントとの関係(オプション)
実装のポイント:
- ハバーサイン公式を使用して、2点間の地理的距離を正確に計算
- 速度から移動タイプを推測(静止、歩行、ジョギング、ランニングなど)
- チェックポイントとの近接性分析も行い、チームが通過済みかどうかも確認
- 周辺ポイントも提供することで、より広い範囲での動きを把握可能
このエンドポイントは、特定地点での移動状況を詳細に分析したい場合に役立ちます。
例えば、チームの移動戦略の分析や、特定地点でのパフォーマンス評価などに使用できます。
"""
@api_view(['GET'])
def analyze_point(request):
"""
指定地点の情報を分析(速度、移動タイプなど)
パラメータ:
- lat: 緯度
- lng: 経度
- team_name: チーム名
- event_code: イベントコード
"""
logger.info("analyze_point called")
# リクエストからパラメータを取得
latitude = request.query_params.get('lat')
longitude = request.query_params.get('lng')
team_name = request.query_params.get('team_name')
event_code = request.query_params.get('event_code')
logger.debug(f"Parameters: lat={latitude}, lng={longitude}, team_name={team_name}, event_code={event_code}")
# パラメータ検証
if not all([latitude, longitude, team_name, event_code]):
logger.warning("Missing required parameters")
return Response({
"status": "ERROR",
"message": "緯度、経度、チーム名、イベントコードが全て必要です"
}, status=status.HTTP_400_BAD_REQUEST)
try:
# 数値型に変換
try:
lat = float(latitude)
lng = float(longitude)
except ValueError:
logger.warning(f"Invalid coordinate values: lat={latitude}, lng={longitude}")
return Response({
"status": "ERROR",
"message": "緯度と経度は数値である必要があります"
}, status=status.HTTP_400_BAD_REQUEST)
# イベントの存在確認
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,
team_name=team_name
).first()
if not entry:
logger.warning(f"Team '{team_name}' not found in event: {event_code}")
return Response({
"status": "ERROR",
"message": "指定されたチームが見つかりません"
}, status=status.HTTP_404_NOT_FOUND)
# 最も近い位置にあるウェイポイントを見つける
# 指定座標からの距離を計算するヘルパー関数
def calculate_distance(waypoint):
wp_lat = waypoint.latitude
wp_lng = waypoint.longitude
# ハバーサイン公式で距離を計算
R = 6371.0 # 地球の半径km
lat1_rad = radians(lat)
lon1_rad = radians(lng)
lat2_rad = radians(wp_lat)
lon2_rad = radians(wp_lng)
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 * 1000 # メートル単位に変換
return distance
# チームの全ウェイポイントを取得
all_waypoints = Waypoint.objects.filter(entry=entry).order_by('recorded_at')
if not all_waypoints.exists():
logger.warning(f"No waypoints found for team {team_name}")
return Response({
"status": "WARNING",
"message": "このチームのウェイポイントデータがありません",
"team_name": team_name,
"event_code": event_code,
"latitude": lat,
"longitude": lng
})
# 各ウェイポイントについて指定座標からの距離を計算
waypoints_with_distance = [(wp, calculate_distance(wp)) for wp in all_waypoints]
# 距離でソート
waypoints_with_distance.sort(key=lambda x: x[1])
# 最も近いウェイポイント
nearest_waypoint, nearest_distance = waypoints_with_distance[0]
# 分析結果
analysis = {
"nearest_point": {
"latitude": nearest_waypoint.latitude,
"longitude": nearest_waypoint.longitude,
"timestamp": nearest_waypoint.recorded_at.strftime("%Y-%m-%d %H:%M:%S"),
"distance": round(nearest_distance, 2), # メートル単位
"altitude": nearest_waypoint.altitude,
"accuracy": nearest_waypoint.accuracy,
"speed": nearest_waypoint.speed
}
}
# 前後のウェイポイントを取得して速度分析
waypoint_index = list(all_waypoints).index(nearest_waypoint)
# 前のウェイポイント(あれば)
if waypoint_index > 0:
prev_waypoint = all_waypoints[waypoint_index - 1]
time_diff = (nearest_waypoint.recorded_at - prev_waypoint.recorded_at).total_seconds()
if time_diff > 0:
# 2点間の距離を計算
prev_distance = calculate_distance(prev_waypoint)
# 速度を計算m/s
speed_mps = abs(nearest_distance - prev_distance) / time_diff
analysis["speed_analysis"] = {
"speed_mps": round(speed_mps, 2),
"speed_kmh": round(speed_mps * 3.6, 2), # km/h に変換
"time_diff_seconds": time_diff
}
# 移動タイプを推測
if speed_mps < 1.5:
mobility_type = "静止またはゆっくり歩行"
elif speed_mps < 2.5:
mobility_type = "歩行"
elif speed_mps < 4.0:
mobility_type = "速歩き"
elif speed_mps < 7.0:
mobility_type = "ジョギング"
elif speed_mps < 12.0:
mobility_type = "ランニング"
else:
mobility_type = "自転車または車両"
analysis["speed_analysis"]["mobility_type"] = mobility_type
# 周辺の他のウェイポイントも取得(時間範囲内で)
# 前後30分以内のウェイポイントを検索
time_range = timedelta(minutes=30)
nearby_waypoints = []
for wp in all_waypoints:
if abs((wp.recorded_at - nearest_waypoint.recorded_at).total_seconds()) <= time_range.total_seconds():
# 距離も近いものだけ1km以内
distance = calculate_distance(wp)
if distance <= 1000:
nearby_waypoints.append({
"latitude": wp.latitude,
"longitude": wp.longitude,
"timestamp": wp.recorded_at.strftime("%Y-%m-%d %H:%M:%S"),
"distance": round(distance, 2),
"altitude": wp.altitude,
"speed": wp.speed
})
analysis["nearby_points"] = nearby_waypoints
analysis["nearby_points_count"] = len(nearby_waypoints)
# チェックポイントとの関係を分析(オプション)
try:
# イベントのチェックポイント定義を取得
event_cps = Location.objects.filter(event=event)
# チームが通過したチェックポイントを取得
team_cps = GpsLog.objects.filter(entry=entry)
if event_cps.exists():
# 指定地点から最も近いチェックポイントを見つける
closest_cp = None
closest_cp_distance = float('inf')
for cp in event_cps:
if cp.latitude is not None and cp.longitude is not None:
# チェックポイントからの距離を計算
cp_lat = cp.latitude
cp_lng = cp.longitude
R = 6371.0
lat1_rad = radians(lat)
lon1_rad = radians(lng)
lat2_rad = radians(cp_lat)
lon2_rad = radians(cp_lng)
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 * 1000 # メートル単位
if distance < closest_cp_distance:
closest_cp = cp
closest_cp_distance = distance
if closest_cp:
# チームがこのチェックポイントを通過したかを確認
team_cp = team_cps.filter(cp_number=closest_cp.cp_number).first()
visited = team_cp is not None
analysis["nearest_checkpoint"] = {
"cp_number": closest_cp.cp_number,
"cp_name": closest_cp.cp_name,
"cp_point": closest_cp.cp_point,
"distance": round(closest_cp_distance, 2),
"visited": visited,
"checkin_time": team_cp.checkin_time.strftime("%Y-%m-%d %H:%M:%S") if visited and team_cp.checkin_time else None
}
except:
# Location モデルがない場合などはスキップ
pass
return Response({
"status": "OK",
"team_name": team_name,
"event_code": event_code,
"latitude": lat,
"longitude": lng,
"analysis": analysis
})
except Exception as e:
logger.error(f"Error in analyze_point: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)