358 lines
10 KiB
Python
358 lines
10 KiB
Python
"""
|
|
GPX Test Route API Views
|
|
GPXシミュレーション用のテストルートデータ取得
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
from datetime import datetime, timedelta
|
|
from rest_framework import status
|
|
from rest_framework.decorators import api_view, permission_classes
|
|
from rest_framework.permissions import AllowAny
|
|
from rest_framework.response import Response
|
|
|
|
from .models import NewEvent2, Location
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@api_view(['GET'])
|
|
@permission_classes([AllowAny])
|
|
def gpx_test_data(request):
|
|
"""
|
|
GPXシミュレーション用のテストルートデータ取得
|
|
|
|
GET /api/routes/gpx-test-data
|
|
|
|
Parameters:
|
|
- event_code: イベントコード
|
|
- route_type: ルートタイプ (sample, short, long)
|
|
"""
|
|
|
|
try:
|
|
event_code = request.GET.get('event_code')
|
|
route_type = request.GET.get('route_type', 'sample')
|
|
|
|
if not event_code:
|
|
return Response({
|
|
'error': 'event_code parameter is required'
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
# イベントの存在確認
|
|
try:
|
|
event = NewEvent2.objects.get(event_name=event_code)
|
|
except NewEvent2.DoesNotExist:
|
|
return Response({
|
|
'error': f'Event "{event_code}" not found'
|
|
}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
# ルートタイプに応じたテストデータ生成
|
|
routes = []
|
|
|
|
if route_type == 'sample':
|
|
routes = _generate_sample_route(event_code)
|
|
elif route_type == 'short':
|
|
routes = _generate_short_route(event_code)
|
|
elif route_type == 'long':
|
|
routes = _generate_long_route(event_code)
|
|
else:
|
|
routes = _generate_sample_route(event_code)
|
|
|
|
return Response({
|
|
'routes': routes,
|
|
'event_code': event_code,
|
|
'route_type': route_type,
|
|
'generated_at': datetime.now().isoformat()
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"GPX test data error: {e}")
|
|
return Response({
|
|
'error': 'Internal server error',
|
|
'message': str(e)
|
|
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
|
|
def _generate_sample_route(event_code):
|
|
"""サンプルルート生成"""
|
|
|
|
# 岐阜市内の主要ポイント
|
|
waypoints = [
|
|
{
|
|
"lat": 35.4122,
|
|
"lng": 136.7514,
|
|
"timestamp": "2025-09-15T10:00:00Z",
|
|
"cp_number": 1,
|
|
"description": "岐阜公園",
|
|
"elevation": 15
|
|
},
|
|
{
|
|
"lat": 35.4089,
|
|
"lng": 136.7581,
|
|
"timestamp": "2025-09-15T10:15:00Z",
|
|
"cp_number": 2,
|
|
"description": "岐阜城天守閣",
|
|
"elevation": 329
|
|
},
|
|
{
|
|
"lat": 35.4091,
|
|
"lng": 136.7456,
|
|
"timestamp": "2025-09-15T10:30:00Z",
|
|
"cp_number": 3,
|
|
"description": "長良川うかいミュージアム",
|
|
"elevation": 12
|
|
},
|
|
{
|
|
"lat": 35.4187,
|
|
"lng": 136.7598,
|
|
"timestamp": "2025-09-15T10:45:00Z",
|
|
"cp_number": 4,
|
|
"description": "岐阜市歴史博物館",
|
|
"elevation": 18
|
|
},
|
|
{
|
|
"lat": 35.4122,
|
|
"lng": 136.7514,
|
|
"timestamp": "2025-09-15T11:00:00Z",
|
|
"cp_number": 0,
|
|
"description": "岐阜公園(ゴール)",
|
|
"elevation": 15
|
|
}
|
|
]
|
|
|
|
gpx_data = _generate_gpx_xml(waypoints, "岐阜市内サンプルルート")
|
|
|
|
return [{
|
|
"route_name": "岐阜市内サンプルルート",
|
|
"description": "チェックポイント1-4を巡回するテストルート",
|
|
"estimated_time": "60分",
|
|
"total_distance": "約3.2km",
|
|
"elevation_gain": "約314m",
|
|
"difficulty": "中級",
|
|
"waypoints": waypoints,
|
|
"gpx_data": gpx_data
|
|
}]
|
|
|
|
|
|
def _generate_short_route(event_code):
|
|
"""短距離ルート生成"""
|
|
|
|
waypoints = [
|
|
{
|
|
"lat": 35.4122,
|
|
"lng": 136.7514,
|
|
"timestamp": "2025-09-15T10:00:00Z",
|
|
"cp_number": 1,
|
|
"description": "岐阜公園(スタート)",
|
|
"elevation": 15
|
|
},
|
|
{
|
|
"lat": 35.4150,
|
|
"lng": 136.7545,
|
|
"timestamp": "2025-09-15T10:10:00Z",
|
|
"cp_number": 2,
|
|
"description": "信長の居館跡",
|
|
"elevation": 25
|
|
},
|
|
{
|
|
"lat": 35.4122,
|
|
"lng": 136.7514,
|
|
"timestamp": "2025-09-15T10:20:00Z",
|
|
"cp_number": 0,
|
|
"description": "岐阜公園(ゴール)",
|
|
"elevation": 15
|
|
}
|
|
]
|
|
|
|
gpx_data = _generate_gpx_xml(waypoints, "岐阜公園周辺ショートルート")
|
|
|
|
return [{
|
|
"route_name": "岐阜公園周辺ショートルート",
|
|
"description": "初心者向けの短距離ルート",
|
|
"estimated_time": "20分",
|
|
"total_distance": "約0.8km",
|
|
"elevation_gain": "約10m",
|
|
"difficulty": "初級",
|
|
"waypoints": waypoints,
|
|
"gpx_data": gpx_data
|
|
}]
|
|
|
|
|
|
def _generate_long_route(event_code):
|
|
"""長距離ルート生成"""
|
|
|
|
waypoints = [
|
|
{
|
|
"lat": 35.4122,
|
|
"lng": 136.7514,
|
|
"timestamp": "2025-09-15T10:00:00Z",
|
|
"cp_number": 1,
|
|
"description": "岐阜公園(スタート)",
|
|
"elevation": 15
|
|
},
|
|
{
|
|
"lat": 35.4089,
|
|
"lng": 136.7581,
|
|
"timestamp": "2025-09-15T10:20:00Z",
|
|
"cp_number": 2,
|
|
"description": "岐阜城天守閣",
|
|
"elevation": 329
|
|
},
|
|
{
|
|
"lat": 35.3978,
|
|
"lng": 136.7456,
|
|
"timestamp": "2025-09-15T10:45:00Z",
|
|
"cp_number": 3,
|
|
"description": "長良川河川敷",
|
|
"elevation": 8
|
|
},
|
|
{
|
|
"lat": 35.4234,
|
|
"lng": 136.7345,
|
|
"timestamp": "2025-09-15T11:15:00Z",
|
|
"cp_number": 4,
|
|
"description": "金華橋",
|
|
"elevation": 10
|
|
},
|
|
{
|
|
"lat": 35.4391,
|
|
"lng": 136.7598,
|
|
"timestamp": "2025-09-15T11:45:00Z",
|
|
"cp_number": 5,
|
|
"description": "護国神社",
|
|
"elevation": 35
|
|
},
|
|
{
|
|
"lat": 35.4187,
|
|
"lng": 136.7698,
|
|
"timestamp": "2025-09-15T12:10:00Z",
|
|
"cp_number": 6,
|
|
"description": "岐阜メモリアルセンター",
|
|
"elevation": 22
|
|
},
|
|
{
|
|
"lat": 35.4122,
|
|
"lng": 136.7514,
|
|
"timestamp": "2025-09-15T12:30:00Z",
|
|
"cp_number": 0,
|
|
"description": "岐阜公園(ゴール)",
|
|
"elevation": 15
|
|
}
|
|
]
|
|
|
|
gpx_data = _generate_gpx_xml(waypoints, "岐阜市内ロングルート")
|
|
|
|
return [{
|
|
"route_name": "岐阜市内ロングルート",
|
|
"description": "上級者向けの長距離チャレンジルート",
|
|
"estimated_time": "150分",
|
|
"total_distance": "約8.5km",
|
|
"elevation_gain": "約321m",
|
|
"difficulty": "上級",
|
|
"waypoints": waypoints,
|
|
"gpx_data": gpx_data
|
|
}]
|
|
|
|
|
|
def _generate_gpx_xml(waypoints, route_name):
|
|
"""GPXファイル形式のXMLを生成"""
|
|
|
|
gpx_header = '''<?xml version="1.0" encoding="UTF-8"?>
|
|
<gpx version="1.1" creator="GifuRogaining" xmlns="http://www.topografix.com/GPX/1/1">
|
|
<metadata>
|
|
<name>{}</name>
|
|
<desc>Generated test route for rogaining simulation</desc>
|
|
<time>{}</time>
|
|
</metadata>'''.format(route_name, datetime.now().isoformat())
|
|
|
|
# トラックセグメント
|
|
track_points = []
|
|
for waypoint in waypoints:
|
|
track_points.append(''' <trkpt lat="{}" lon="{}">
|
|
<ele>{}</ele>
|
|
<time>{}</time>
|
|
<name>CP{} - {}</name>
|
|
</trkpt>'''.format(
|
|
waypoint['lat'],
|
|
waypoint['lng'],
|
|
waypoint.get('elevation', 0),
|
|
waypoint['timestamp'],
|
|
waypoint['cp_number'],
|
|
waypoint['description']
|
|
))
|
|
|
|
# ウェイポイント
|
|
waypoint_elements = []
|
|
for waypoint in waypoints:
|
|
waypoint_elements.append(''' <wpt lat="{}" lon="{}">
|
|
<ele>{}</ele>
|
|
<time>{}</time>
|
|
<name>CP{}</name>
|
|
<desc>{}</desc>
|
|
<sym>Flag, Blue</sym>
|
|
</wpt>'''.format(
|
|
waypoint['lat'],
|
|
waypoint['lng'],
|
|
waypoint.get('elevation', 0),
|
|
waypoint['timestamp'],
|
|
waypoint['cp_number'],
|
|
waypoint['description']
|
|
))
|
|
|
|
gpx_content = f'''{gpx_header}
|
|
|
|
<trk>
|
|
<name>{route_name}</name>
|
|
<desc>Test route for rogaining simulation</desc>
|
|
<trkseg>
|
|
{chr(10).join(track_points)}
|
|
</trkseg>
|
|
</trk>
|
|
|
|
{chr(10).join(waypoint_elements)}
|
|
|
|
</gpx>'''
|
|
|
|
return gpx_content
|
|
|
|
|
|
@api_view(['GET'])
|
|
@permission_classes([AllowAny])
|
|
def available_routes(request):
|
|
"""利用可能なテストルート一覧取得"""
|
|
|
|
event_code = request.GET.get('event_code')
|
|
|
|
routes_info = [
|
|
{
|
|
"route_type": "sample",
|
|
"name": "岐阜市内サンプルルート",
|
|
"description": "標準的なテストルート",
|
|
"estimated_time": "60分",
|
|
"difficulty": "中級",
|
|
"checkpoint_count": 4
|
|
},
|
|
{
|
|
"route_type": "short",
|
|
"name": "岐阜公園周辺ショートルート",
|
|
"description": "初心者向けの短距離ルート",
|
|
"estimated_time": "20分",
|
|
"difficulty": "初級",
|
|
"checkpoint_count": 2
|
|
},
|
|
{
|
|
"route_type": "long",
|
|
"name": "岐阜市内ロングルート",
|
|
"description": "上級者向けの長距離ルート",
|
|
"estimated_time": "150分",
|
|
"difficulty": "上級",
|
|
"checkpoint_count": 6
|
|
}
|
|
]
|
|
|
|
return Response({
|
|
"available_routes": routes_info,
|
|
"event_code": event_code,
|
|
"total_routes": len(routes_info)
|
|
})
|