402 lines
16 KiB
Python
Executable File
402 lines
16 KiB
Python
Executable File
# 既存のインポート部分に追加
|
||
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)
|
||
|