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]