co-maps/tools/python/ios_simulator_load_gpx.py
2025-11-22 13:58:55 +01:00

168 lines
No EOL
5.8 KiB
Python

#!/usr/bin/env python3
"""
GPX to iOS Simulator simctl location command
Converts a GPX file to simctl location start command for realistic iOS location simulation.
Tested with CoMaps exported tracks
Usage:
python gpx_to_simctl.py test_route.gpx
"""
import argparse
import xml.etree.ElementTree as ET
from pathlib import Path
import sys
import subprocess
def extract_track_points_from_gpx(gpx_file: Path):
"""Extract track points from GPX file."""
tree = ET.parse(gpx_file)
root = tree.getroot()
points = []
# Find all elements with lat/lon attributes
for elem in root.findall('.//*[@lat][@lon]'):
lat = float(elem.get('lat'))
lon = float(elem.get('lon'))
points.append((lat, lon))
return points
def generate_simctl_command(points, speed_kmh=60, interval=0.1, distance=None, device="booted"):
"""Generate simctl location start command."""
if len(points) < 2:
raise ValueError("Need at least 2 waypoints for simctl location start")
# Convert km/h to m/s
speed_mps = speed_kmh / 3.6
# Format waypoints as lat,lon pairs
waypoint_strings = [f"{lat:.6f},{lon:.6f}" for lat, lon in points]
# Build command
cmd = ["xcrun", "simctl", "location", device, "start"]
cmd.append(f"--speed={speed_mps:.2f}")
if distance:
cmd.append(f"--distance={distance}")
else:
cmd.append(f"--interval={interval}")
cmd.extend(waypoint_strings)
return cmd
def main():
parser = argparse.ArgumentParser(
description="Convert GPX file to simctl location start command",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python gpx_to_simctl.py test_route.gpx --speed 60 --interval 0.1
python gpx_to_simctl.py test_route.gpx --speed 80 --distance 10 --clear-first
python gpx_to_simctl.py test_route.gpx --speed 50 --dry-run
"""
)
parser.add_argument('gpx_file', help='Input GPX file')
parser.add_argument('--speed', type=float, default=60,
help='Speed in km/h (default: 60)')
parser.add_argument('--interval', type=float, default=0.1,
help='Update interval in seconds (default: 0.1)')
parser.add_argument('--distance', type=float,
help='Update distance in meters (overrides --interval)')
parser.add_argument('--device', default='booted',
help='Target device (default: booted)')
parser.add_argument('--dry-run', action='store_true',
help='Show command without executing (default: execute)')
parser.add_argument('--clear-first', action='store_true',
help='Clear existing location before starting')
args = parser.parse_args()
# Validate input file
gpx_file = Path(args.gpx_file)
if not gpx_file.exists():
print(f"Error: GPX file '{gpx_file}' not found", file=sys.stderr)
return 1
try:
# Extract waypoints
points = extract_track_points_from_gpx(gpx_file)
print(f"Extracted {len(points)} waypoints from {gpx_file}")
if len(points) < 2:
print("Error: Need at least 2 waypoints for location simulation", file=sys.stderr)
return 1
# Generate command
cmd = generate_simctl_command(
points,
speed_kmh=args.speed,
interval=args.interval,
distance=args.distance,
device=args.device
)
# Show command
print(f"\nGenerated simctl command:")
print(" ".join(cmd))
# Calculate simulation info
speed_mps = args.speed / 3.6
total_distance = 0
for i in range(1, len(points)):
lat1, lon1 = points[i-1]
lat2, lon2 = points[i]
# Simple distance approximation
total_distance += ((lat2-lat1)**2 + (lon2-lon1)**2)**0.5 * 111000 # rough conversion to meters
duration = total_distance / speed_mps
print(f"\nSimulation info:")
print(f" Speed: {args.speed} km/h ({speed_mps:.1f} m/s)")
print(f" Waypoints: {len(points)}")
print(f" Estimated distance: {total_distance/1000:.2f} km")
print(f" Estimated duration: {duration:.0f} seconds ({duration/60:.1f} minutes)")
if args.distance:
print(f" Update distance: {args.distance}m")
else:
print(f" Update interval: {args.interval}s")
# Execute by default unless dry-run
if args.dry_run:
print(f"\n[DRY RUN] Command that would be executed:")
print(f" {' '.join(cmd)}")
if args.clear_first:
clear_cmd = ["xcrun", "simctl", "location", args.device, "clear"]
print(f" (would clear location first: {' '.join(clear_cmd)})")
else:
print(f"\nExecuting command...")
# Clear location first if requested
if args.clear_first:
clear_cmd = ["xcrun", "simctl", "location", args.device, "clear"]
print("Clearing existing location...")
subprocess.run(clear_cmd, check=True)
# Execute the start command
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
print("✅ Location simulation started successfully!")
if result.stdout.strip():
print(result.stdout.strip())
else:
print(f"❌ Error executing command:")
print(result.stderr.strip())
return 1
return 0
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
if __name__ == '__main__':
sys.exit(main())