# 既存のインポート部分に追加 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__) """ 解説 この実装では以下の処理を行っています: 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_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) # チームがスタートしているか確認(オプション) if not hasattr(entry, 'start_info'): 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) # チームの存在確認 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) # ウェイポイントデータを取得(時間順) 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, "image_url": cp.image_address, "is_service_checked": getattr(cp, 'is_service_checked', False) } # チェックポイントの座標情報を取得(Locationモデルがある場合) try: event_cp = Location.objects.filter( event=event, 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.zekken_number, "class_name": entry.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 = Location.objects.filter( event=event, 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)