Files
rogaining_srv/rog/views_apis/api_waypoint.py
2025-09-02 23:14:14 +09:00

985 lines
41 KiB
Python
Executable File
Raw Permalink 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, Location2025, GpsLog
import logging
import json
from django.db.models import F, Q
from django.db import transaction
from django.conf import settings
import os
from urllib.parse import urljoin
logger = logging.getLogger(__name__)
"""
解説
この実装では以下の処理を行っています:
1.ウェイポイントデータを保存するためのモデル Waypoint を定義
2.POST リクエストからチーム名、イベントコード、ウェイポイントデータ配列を取得
3.データのバリデーションを行う
- 必要なパラメータが全て揃っているか
- ウェイポイントデータが配列形式か
- 各ウェイポイントに必要な情報(緯度、経度、記録時間)が含まれているか
4.イベントとチームの存在確認
5.各ウェイポイントを処理
- 数値型・日時型の変換
- 重複チェック(同じ記録時間のデータは無視)
- データベースに保存
6.処理結果の統計(保存数、スキップ数、無効データ数)を含む応答を返す
この実装により、アプリからチームの位置情報を送信し、サーバーで保存することができます。
保存されたデータは、後でルート表示や分析などに使用できます。
"""
@api_view(['POST'])
def get_waypoint_datas_from_rogapp(request):
"""
アプリからウェイポイントデータを受信し保存
パラメータ:
- team_name: チーム名
- event_code: イベントコード
- waypoints: ウェイポイントデータの配列
"""
logger.info("get_waypoint_datas_from_rogapp called")
# リクエストからパラメータを取得
try:
data = request.data
team_name = data.get('team_name')
event_code = data.get('event_code')
waypoints_data = data.get('waypoints')
# JSONとして送信された場合はデコード
if isinstance(waypoints_data, str):
try:
waypoints_data = json.loads(waypoints_data)
except json.JSONDecodeError:
logger.warning("Invalid JSON format for waypoints")
return Response({
"status": "ERROR",
"message": "ウェイポイントデータのJSONフォーマットが無効です"
}, status=status.HTTP_400_BAD_REQUEST)
logger.debug(f"Parameters: team_name={team_name}, event_code={event_code}, "
f"waypoints_count={len(waypoints_data) if waypoints_data else 0}")
except Exception as e:
logger.warning(f"Error parsing request data: {str(e)}")
return Response({
"status": "ERROR",
"message": "リクエストデータの解析に失敗しました"
}, status=status.HTTP_400_BAD_REQUEST)
# パラメータ検証
if not all([team_name, event_code, waypoints_data]):
logger.warning("Missing required parameters")
return Response({
"status": "ERROR",
"message": "チーム名、イベントコード、ウェイポイントデータが必要です"
}, status=status.HTTP_400_BAD_REQUEST)
# ウェイポイントデータの検証
if not isinstance(waypoints_data, list):
logger.warning("Waypoints data is not a list")
return Response({
"status": "ERROR",
"message": "ウェイポイントデータはリスト形式である必要があります"
}, status=status.HTTP_400_BAD_REQUEST)
if len(waypoints_data) == 0:
logger.warning("Empty waypoints list")
return Response({
"status": "WARNING",
"message": "ウェイポイントリストが空です",
"saved_count": 0
})
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,
team__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)
# チームがスタートしているか確認
start_record = GpsLog.objects.filter(
zekken_number=entry.zekken_number,
event_code=entry.event.event_name,
cp_number="START",
serial_number=0
).first()
if not start_record:
logger.warning(f"Team {team_name} has not started yet")
# 必要に応じてエラーを返すか、自動的にスタート処理を行う
# トランザクション開始
saved_count = 0
skipped_count = 0
invalid_count = 0
with transaction.atomic():
for waypoint in waypoints_data:
try:
# 必須フィールドの検証
lat = waypoint.get('latitude')
lng = waypoint.get('longitude')
recorded_at_str = waypoint.get('recorded_at')
if not all([lat, lng, recorded_at_str]):
logger.warning(f"Missing required fields in waypoint: {waypoint}")
invalid_count += 1
continue
# 数値型の検証
try:
lat = float(lat)
lng = float(lng)
except (ValueError, TypeError):
logger.warning(f"Invalid coordinate values: lat={lat}, lng={lng}")
invalid_count += 1
continue
# 日時の解析
try:
# 複数の日付形式に対応
recorded_at = None
for date_format in [
"%Y-%m-%dT%H:%M:%S.%fZ", # ISO形式ミリ秒付き
"%Y-%m-%dT%H:%M:%SZ", # ISO形式
"%Y-%m-%d %H:%M:%S", # 標準形式
"%Y/%m/%d %H:%M:%S" # スラッシュ区切り
]:
try:
if recorded_at_str.endswith('Z'):
# UTCからローカルタイムに変換する必要がある場合
dt = datetime.strptime(recorded_at_str, date_format)
# ここでUTCからローカルタイムへの変換を行う場合
recorded_at = dt
else:
recorded_at = datetime.strptime(recorded_at_str, date_format)
break
except ValueError:
continue
if recorded_at is None:
raise ValueError(f"No matching date format for: {recorded_at_str}")
except ValueError as e:
logger.warning(f"Invalid date format: {recorded_at_str} - {str(e)}")
invalid_count += 1
continue
# 重複チェック(同じ記録時間のウェイポイントは無視)
if Waypoint.objects.filter(
entry=entry,
recorded_at=recorded_at
).exists():
logger.debug(f"Skipping duplicate waypoint at: {recorded_at_str}")
skipped_count += 1
continue
# オプションフィールドの取得
altitude = waypoint.get('altitude')
accuracy = waypoint.get('accuracy')
speed = waypoint.get('speed')
# ウェイポイントの保存
Waypoint.objects.create(
entry=entry,
latitude=lat,
longitude=lng,
altitude=float(altitude) if altitude not in [None, ''] else None,
accuracy=float(accuracy) if accuracy not in [None, ''] else None,
speed=float(speed) if speed not in [None, ''] else None,
recorded_at=recorded_at
)
saved_count += 1
except Exception as e:
logger.error(f"Error processing waypoint: {str(e)}")
invalid_count += 1
logger.info(f"Processed waypoints for team {team_name}: "
f"saved={saved_count}, skipped={skipped_count}, invalid={invalid_count}")
return Response({
"status": "OK",
"message": "ウェイポイントデータを正常に保存しました",
"team_name": team_name,
"event_code": event_code,
"total_waypoints": len(waypoints_data),
"saved_count": saved_count,
"skipped_count": skipped_count,
"invalid_count": invalid_count
})
except Exception as e:
logger.error(f"Error in get_waypoint_datas_from_rogapp: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
"""
解説
この実装では以下の処理を行っています:
1.チーム名とイベントコードのパラメータを受け取ります
2.パラメータが不足している場合はエラーを返します
3.指定されたイベントとチームの存在を確認します
4.チームの移動経路データを取得します:
- ウェイポイントデータ(記録時間順)
- チェックポイント通過情報(時間順)
- スタート情報とゴール情報
5.移動経路の統計情報を計算します:
- 総移動距離
- ウェイポイント数
- チェックポイント数
6.GeoJSON形式のルートデータを作成します
7.全てのデータを含む応答を返します
実装のポイント:
- ウェイポイントとチェックポイントの両方をルートポイントとして統合
- チェックポイントの座標はLocationモデルから取得存在する場合
- ハバーサイン公式を使用して2点間の距離を計算し、総移動距離を算出
- GeoJSON形式のデータも含め、様々な用途に対応できる形式でデータを提供
このエンドポイントにより、チームの移動経路を視覚化したり分析したりすることができます。
"""
@api_view(['GET'])
def get_route(request):
"""
指定チームのルート情報を取得
パラメータ:
- team: チーム名
- event_code: イベントコード
"""
logger.info("get_route called")
# リクエストからパラメータを取得
team_name = request.query_params.get('team')
event_code = request.query_params.get('event_code')
logger.debug(f"Parameters: team={team_name}, event_code={event_code}")
# パラメータ検証
if not all([team_name, event_code]):
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)
logger.debug(f"Event found: {event.event_name} (id: {event.id})")
# チームの存在確認
entry = Entry.objects.filter(
event=event,
team__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)
logger.debug(f"Entry found: {entry.id}, team: {entry.team.team_name}")
# 簡略化されたレスポンスでテストDBクエリなし
return Response({
"status": "OK",
"message": "get_route function is working",
"team_name": team_name,
"event_code": event_code,
"entry_id": entry.id
})
except Exception as e:
logger.error(f"Error in get_route: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# ウェイポイントを処理
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,
"image_url": cp.image_address,
"is_service_checked": getattr(cp, 'is_service_checked', False)
}
# チェックポイントの座標情報を取得Location2025モデルがある場合
try:
event_cp = Location2025.objects.filter(
event_id=event.id,
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
cp_data["is_service_cp"] = event_cp.is_service_cp
# ルートポイントとしても追加
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,
"cp_name": event_cp.cp_name
})
except:
# Locationモデルが存在しないか、座標が設定されていない場合
pass
checkpoint_data.append(cp_data)
# ルートの統計情報を計算
total_distance = 0
if len(route_points) >= 2:
from math import sin, cos, sqrt, atan2, radians
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
# GeoJSON形式のルートデータを作成オプション
geo_json = {
"type": "FeatureCollection",
"features": [
{
"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": team_name,
"total_distance": round(total_distance, 2), # km単位で小数点以下2桁まで
"waypoint_count": waypoints.count(),
"checkpoint_count": checkpoints.count()
}
}
]
}
return Response({
"status": "OK",
"team_info": {
"team_name": team_name,
"zekken_number": entry.team.zekken_number,
"class_name": entry.team.class_name,
"event_code": event_code
},
"start_info": start_info,
"goal_info": goal_info,
"route_statistics": {
"total_distance": round(total_distance, 2), # km単位
"total_waypoints": waypoints.count(),
"total_checkpoints": checkpoints.count()
},
"checkpoints": checkpoint_data,
"route_points": route_points,
"geo_json": geo_json
})
except Exception as e:
logger.error(f"Error in get_route: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
"""
解説
この実装では以下の処理を行っています:
1.ゼッケン番号とイベントコードのパラメータを受け取ります
2.オプションで limit返す位置情報の最大数と time_range時間範囲のパラメータも受け取ります
3.パラメータが不足している場合はエラーを返します
4.指定されたイベントとチームの存在を確認します
5.チームのウェイポイントデータを取得します:
- 新しい順(降順)にソート
- 時間範囲でフィルタリング(指定された場合)
- 件数制限(指定された場合)
6.チームの基本情報、最新の位置情報、位置情報履歴を含む応答を作成します
7.GeoJSON形式のデータも提供します
- 移動経路を表す LineString フィーチャー
- 各位置を表す Point フィーチャー
この実装は /getRoute エンドポイントに似ていますが、以下の点で異なります:
- 位置情報の取得に焦点を当てています(チェックポイント情報は含まれません)
- より新しいデータを優先的に返します(降順ソート)
- 時間範囲や件数制限のオプションがあります
- 移動経路の統計情報(距離など)は計算しません
- このエンドポイントは、リアルタイムマップ表示やチームの動きを追跡する場合に便利です。
"""
# 既存のインポート部分に追加
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
import logging
from django.db.models import F, Q
from datetime import datetime, timedelta
logger = logging.getLogger(__name__)
@api_view(['GET'])
def fetch_user_locations(request):
"""
ユーザーの位置情報履歴を取得
パラメータ:
- zekken_number: ゼッケン番号
- event_code: イベントコード
"""
logger.info("fetch_user_locations called")
# リクエストからパラメータを取得
zekken_number = request.query_params.get('zekken_number')
event_code = request.query_params.get('event_code')
# 追加のオプションパラメータ
limit = request.query_params.get('limit')
if limit:
try:
limit = int(limit)
except ValueError:
limit = None
time_range = request.query_params.get('time_range') # 例: "1h", "30m", "2d"
logger.debug(f"Parameters: zekken_number={zekken_number}, event_code={event_code}, "
f"limit={limit}, time_range={time_range}")
# パラメータ検証
if not all([zekken_number, event_code]):
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_query = Waypoint.objects.filter(entry=entry)
# 時間範囲でフィルタリング(オプション)
if time_range:
# 時間範囲をパース (例: "1h", "30m", "2d")
time_value = ""
time_unit = ""
for char in time_range:
if char.isdigit():
time_value += char
else:
time_unit += char
if time_value and time_unit:
time_value = int(time_value)
now = datetime.now()
if time_unit.lower() == 'h':
start_time = now - timedelta(hours=time_value)
elif time_unit.lower() == 'm':
start_time = now - timedelta(minutes=time_value)
elif time_unit.lower() == 'd':
start_time = now - timedelta(days=time_value)
else:
# デフォルトは1時間
start_time = now - timedelta(hours=1)
waypoints_query = waypoints_query.filter(recorded_at__gte=start_time)
# 時間順にソート
waypoints_query = waypoints_query.order_by('-recorded_at')
# 件数制限(オプション)
if limit:
waypoints_query = waypoints_query[:limit]
# データの取得
waypoints = list(waypoints_query)
# チームのスタート・ゴール情報
start_time = None
if hasattr(entry, 'start_info'):
start_time = entry.start_info.start_time
goal_time = None
if hasattr(entry, 'goal_info'):
goal_time = entry.goal_info.goal_time
# レスポンスデータの構築
location_data = []
for wp in waypoints:
location = {
"latitude": wp.latitude,
"longitude": wp.longitude,
"timestamp": wp.recorded_at.strftime("%Y-%m-%d %H:%M:%S"),
}
# オプションフィールドがあれば追加
if wp.altitude is not None:
location["altitude"] = wp.altitude
if wp.accuracy is not None:
location["accuracy"] = wp.accuracy
if wp.speed is not None:
location["speed"] = wp.speed
location_data.append(location)
# 最後の位置情報
latest_location = None
if location_data:
latest_location = location_data[0] # すでに新しい順にソート済み
# GeoJSON形式のデータも作成
geo_json = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[point["longitude"], point["latitude"]]
for point in reversed(location_data) # 時系列順に並べ直す
]
},
"properties": {
"team_name": entry.team_name,
"zekken_number": zekken_number,
"point_count": len(location_data)
}
}
]
}
# ポイントを個別のフィーチャーとしても追加
for point in location_data:
geo_json["features"].append({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [point["longitude"], point["latitude"]]
},
"properties": {
"timestamp": point["timestamp"],
"speed": point.get("speed"),
"altitude": point.get("altitude")
}
})
return Response({
"status": "OK",
"team_info": {
"team_name": entry.team_name,
"zekken_number": zekken_number,
"class_name": entry.class_name,
"event_code": event_code,
"start_time": start_time.strftime("%Y-%m-%d %H:%M:%S") if start_time else None,
"goal_time": goal_time.strftime("%Y-%m-%d %H:%M:%S") if goal_time else None
},
"latest_location": latest_location,
"locations": location_data,
"total_locations": len(location_data),
"geo_json": geo_json
})
except Exception as e:
logger.error(f"Error in fetch_user_locations: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
"""
解説
この実装では以下の処理を行っています:
1.イベントコードとオプションでクラス名のパラメータを受け取ります
2.パラメータが不足している場合はエラーを返します
3.指定されたイベントの存在を確認します
4.イベント内のチームをクエリします:
- スタート情報があるチームのみを対象
- クラス名が指定された場合はそのクラスに所属するチームのみに限定
5.各チームについて以下の情報を取得します:
- チームの基本情報(名前、ゼッケン番号、クラス)
- スタート情報とゴール情報
- ウェイポイントとチェックポイントのデータ
- ルートの統計情報(総距離、ウェイポイント数、チェックポイント数)
6.全チームの情報と、GeoJSON形式のデータを含む応答を返します
実装のポイント:
- レスポンスサイズを抑えるため、各チームのルートポイント詳細は含めず、統計情報のみを提供
- GeoJSON形式のデータでは、各チームのルートを1つのフィーチャーとして表現
- 各チームのデータ処理中にエラーが発生した場合でも、他のチームの処理は継続する例外処理
- オプションでクラス名によるフィルタリングが可能
このエンドポイントは、管理画面での全チームの一覧表示や、全チームのルートを1つの地図上に表示したい場合に役立ちます。
"""
@api_view(['GET'])
def get_all_routes(request):
"""
指定イベントの全チームのルート情報を取得
パラメータ:
- event_code: イベントコード
- class_name: クラス名(省略可)
"""
logger.info("get_all_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')
logger.debug(f"Parameters: event_code={event_code}, class_name={class_name}")
# パラメータ検証
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)
# 対象チームのクエリ作成(クラス名でフィルタリング)
entries_query = Entry.objects.filter(event=event)
if class_name:
entries_query = entries_query.filter(class_name=class_name)
# スタート情報があるチームだけ対象にする
entries_with_start = entries_query.filter(start_info__isnull=False)
if not entries_with_start.exists():
logger.warning(f"No teams with start info found for event: {event_code}")
return Response({
"status": "WARNING",
"message": "スタート済みのチームが見つかりません",
"teams": [],
"total_teams": 0
})
# 全チームの情報とルートを取得
teams_data = []
geo_json_features = []
for entry in entries_with_start:
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 = Location2025.objects.filter(
event_id=event.id,
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 = {
"team_name": entry.team_name,
"zekken_number": entry.zekken_number,
"class_name": entry.class_name,
"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,
# ルートポイントは量が多いため、必要に応じて個別ルート取得APIを使用する形に
"route_points_count": len(route_points)
}
teams_data.append(team_data)
# GeoJSONフィーチャーを追加
if route_points:
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,
"total_distance": round(total_distance, 2),
"waypoint_count": waypoints.count(),
"checkpoint_count": checkpoints.count(),
"score": goal_info["score"] if goal_info else None
}
})
except Exception as e:
logger.error(f"Error processing route for team {entry.team_name}: {str(e)}")
# エラーが発生したチームはスキップ
continue
# GeoJSON形式のデータを作成
geo_json = {
"type": "FeatureCollection",
"features": geo_json_features
}
return Response({
"status": "OK",
"event_code": event_code,
"class_name": class_name,
"total_teams": len(teams_data),
"teams": teams_data,
"geo_json": geo_json
})
except Exception as e:
logger.error(f"Error in get_all_routes: {str(e)}")
return Response({
"status": "ERROR",
"message": "サーバーエラーが発生しました"
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)