initial setting at 20-Aug-2025
This commit is contained in:
401
rog/views_apis/api_events.py
Executable file
401
rog/views_apis/api_events.py
Executable file
@ -0,0 +1,401 @@
|
||||
# 既存のインポート部分に追加
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user