1
#!/usr/bin/env python3
"""
GPX Area Coverage Generator

This script generates a GPX file with a zig-zag pattern to cover a rectangular area
defined by four longitude/latitude points. The zig-zag pattern ensures the navigation app
will download all map tiles within the specified area.

Usage:
    python gpx_generator.py min_lon min_lat max_lon max_lat [spacing] [output_file]

Arguments:
    min_lon, min_lat: The southwest corner of the bounding box
    max_lon, max_lat: The northeast corner of the bounding box
    spacing: Distance between zig-zag lines in meters (default: 200)
    output_file: Name of the output GPX file (default: "route.gpx")
"""

import argparse
import math
from datetime import datetime

def haversine_distance(lat1, lon1, lat2, lon2):
    """Calculate the great-circle distance between two points in meters."""
    R = 6371000  # Earth radius in meters
    
    # Convert decimal degrees to radians
    lat1, lon1, lat2, lon2 = map(math.radians, [lat1, lon1, lat2, lon2])
    
    # Haversine formula
    dlat = lat2 - lat1
    dlon = lon2 - lon1
    a = math.sin(dlat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon/2)**2
    c = 2 * math.asin(math.sqrt(a))
    
    return R * c

def calculate_new_point(lat, lon, bearing, distance):
    """Calculate new GPS coordinates given a starting point, bearing (in degrees), and distance (in meters)."""
    R = 6371000  # Earth radius in meters
    
    # Convert to radians
    lat1 = math.radians(lat)
    lon1 = math.radians(lon)
    bearing = math.radians(bearing)
    
    # Calculate angular distance
    angular_distance = distance / R
    
    # Calculate new latitude
    lat2 = math.asin(math.sin(lat1) * math.cos(angular_distance) +
                     math.cos(lat1) * math.sin(angular_distance) * math.cos(bearing))
    
    # Calculate new longitude
    lon2 = lon1 + math.atan2(math.sin(bearing) * math.sin(angular_distance) * math.cos(lat1),
                           math.cos(angular_distance) - math.sin(lat1) * math.sin(lat2))
    
    # Convert back to degrees
    lat2 = math.degrees(lat2)
    lon2 = math.degrees(lon2)
    
    return lat2, lon2

def generate_gpx(min_lon, min_lat, max_lon, max_lat, spacing, output_file):
    """Generate a GPX file with a zig-zag pattern covering the specified area."""
    
    # Calculate the width and height of the bounding box
    width_meters = haversine_distance(min_lat, min_lon, min_lat, max_lon)
    height_meters = haversine_distance(min_lat, min_lon, max_lat, min_lon)
    
    # Calculate number of horizontal passes needed
    num_passes = max(2, int(height_meters / spacing) + 1)
    
    # Create the zig-zag pattern
    waypoints = []
    
    # Start at the bottom-left corner
    lat, lon = min_lat, min_lon
    waypoints.append((lat, lon))
    
    # Calculate the lat increment for each pass
    lat_increment = (max_lat - min_lat) / (num_passes - 1)
    
    # Generate the zig-zag pattern
    for i in range(num_passes):
        current_lat = min_lat + i * lat_increment
        
        # Even-numbered passes go from left to right
        if i % 2 == 0:
            waypoints.append((current_lat, min_lon))
            waypoints.append((current_lat, max_lon))
        # Odd-numbered passes go from right to left
        else:
            waypoints.append((current_lat, max_lon))
            waypoints.append((current_lat, min_lon))
    
    # Write the GPX file
    with open(output_file, 'w') as f:
        # GPX header
        f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
        f.write('<gpx version="1.1" creator="GPX Area Coverage Generator" xmlns="http://www.topografix.com/GPX/1/1">\n')
        f.write('  <metadata>\n')
        f.write(f'    <name>Area Coverage Route</name>\n')
        f.write(f'    <time>{datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")}</time>\n')
        f.write('  </metadata>\n')
        f.write('  <trk>\n')
        f.write('    <name>Coverage Route</name>\n')
        f.write('    <trkseg>\n')
        
        # Write track points
        for lat, lon in waypoints:
            f.write(f'      <trkpt lat="{lat}" lon="{lon}"></trkpt>\n')
        
        # GPX footer
        f.write('    </trkseg>\n')
        f.write('  </trk>\n')
        f.write('</gpx>\n')
    
    print(f"GPX file generated with {len(waypoints)} waypoints.")
    print(f"Coverage: Width = {width_meters:.1f}m, Height = {height_meters:.1f}m")
    print(f"Number of passes: {num_passes} (spacing = {spacing}m)")
    print(f"Output written to: {output_file}")

def main():
    parser = argparse.ArgumentParser(description="Generate a GPX file with a zig-zag pattern covering a specified area.")
    parser.add_argument("min_lon", type=float, help="Minimum longitude (western boundary)")
    parser.add_argument("min_lat", type=float, help="Minimum latitude (southern boundary)")
    parser.add_argument("max_lon", type=float, help="Maximum longitude (eastern boundary)")
    parser.add_argument("max_lat", type=float, help="Maximum latitude (northern boundary)")
    parser.add_argument("-s", "--spacing", type=float, default=200.0, help="Spacing between zig-zag lines in meters (default: 200)")
    parser.add_argument("-o", "--output", default="route.gpx", help="Output GPX filename (default: route.gpx)")
    
    args = parser.parse_args()
    
    generate_gpx(args.min_lon, args.min_lat, args.max_lon, args.max_lat, args.spacing, args.output)

if __name__ == "__main__":
    main()

For immediate assistance, please email our customer support: [email protected]

Download RAW File