当前位置: 首页 > news >正文

【Python】运动路线记录GPX文件的操作API函数,以及相关GUI界面(支持复制、拼接、数据生成、修改,SRT字幕生成等功能)

【Python】运动路线记录GPX文件的操作API函数,以及相关GUI界面(支持复制、拼接、数据生成、修改,SRT字幕生成等功能)

更新以gitee为准
gitee

文章目录

  • 效果展示
  • APIs
  • GUI界面
  • 附录:列表的赋值类型和py打包
    • 列表赋值
      • BUG复现
      • 代码改进
      • 优化
      • 总结
    • py打包

效果展示

在这里插入图片描述
拼接数据生成效果:
在这里插入图片描述

在这里插入图片描述

APIs

# -*- coding: utf-8 -*-
"""
Created on Thu May 11 15:33:28 2023@author: ZHOU
"""
import time
import numpy as np
from pandas import DataFrame,notnull
import random
from decimal import Decimaldata_choice_1=[-5,-4,-4,-3,-3,-3,-2,-2,-2,-1,-1,-1,-1,0,0,0,0,1,1,1,1,2,2,2,3,3,3,4,4,5]data_choice_2=[Decimal("-0.000009"),Decimal("-0.000006"),Decimal("-0.000006"),\Decimal("-0.000003"),Decimal("-0.000003"),Decimal("-0.000003"),\Decimal("-0.000001"),Decimal("0.000001"),Decimal("0"),\Decimal("0.000003"),Decimal("0.000003"),Decimal("0.000003"),\Decimal("0.000009"),Decimal("0.000006"),Decimal("0.000006")]# 定义各种GPX文件标志
COROS_GPX_FLAG = '''<?xml version="1.0" encoding="UTF-8"?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:gpxdata="http://www.cluetrust.com/XML/GPXDATA/1/0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 https://www.topografix.com/GPX/1/1/gpx.xsd http://www.cluetrust.com/XML/GPXDATA/1/0 https://www.cluetrust.com/Schemas/gpxdata10.xsd" creator="COROS Wearables" version="1.1"><metadata><link href="https://www.coros.com"><text>COROS</text></link>'''GARMIN_GPX_FLAG = '''<?xml version="1.0" encoding="UTF-8"?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" creator="Garmin Connect" version="1.1"><metadata><link href="https://connect.garmin.com"><text>Garmin Connect</text></link>'''STRAVA_GPX_FLAG = '''<?xml version="1.0" encoding="UTF-8"?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" creator="StravaGPX" version="1.1"><metadata><link href="https://www.strava.com"><text>Strava</text></link>'''STANDARD_GPX_FLAG = '''<?xml version="1.0" encoding="UTF-8"?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" creator="GPX Editor" version="1.1"><metadata><link href="https://example.com"><text>GPX Editor</text></link>'''# GPX标志映射字典
GPX_FLAGS = {"COROS": COROS_GPX_FLAG,"Garmin": GARMIN_GPX_FLAG,"Strava": STRAVA_GPX_FLAG,"Standard": STANDARD_GPX_FLAG
}'''gpx文件格式说明
<?xml version="1.0" encoding="UTF-8"?> # 版本
<gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" creator="StravaGPX" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3"><metadata><time>2025-08-30T10:55:24Z</time> # 运动时间</metadata><trk><name>傍晚骑行</name> # 名称<type>cycling</type> # 运动类型<trkseg> # 开始轨迹记录# trkpt属性 数据点记录# 每次记录一个点 必须要有坐标信息 ele是海拔 如果没有time 则只能做路书 而不能做运动记录# extensions属性中为额外信息 如心率等<trkpt lat="30.5771990" lon="114.3662630"><ele>30.7</ele> # 海拔<time>2025-08-30T10:55:24Z</time> # 时间<extensions><power>245</power><cadence>80</cadence><heartrate>120</heartrate></extensions></trkpt> # 结束记录一个点# 第二个点记录<trkpt lat="30.5771990" lon="114.3662650"><ele>30.7</ele><time>2025-08-30T10:55:24Z</time><extensions><power>245</power><cadence>80</cadence><heartrate>120</heartrate></extensions></trkpt>#...  # 其他点记录# 最后一个点记录<trkpt lat="30.5860920" lon="114.3705440"><ele>27.4</ele><time>2025-08-30T12:05:35Z</time><extensions><power>245</power><cadence>80</cadence><heartrate>120</heartrate></extensions></trkpt></trkseg>  # 结束轨迹记录</trk>
</gpx>''''''data keywords gpx文件关键字extensions的数据类型说明
心率 "heartrate"
踏频 "cadence"
距离 "distance"
功率 "power"
速度 "speed"基础记录方式:
<extensions><power>245</power><cadence>80</cadence><distance>1000</distance><heartrate>120</heartrate>
</extensions>
或多个extensions属性分开记录:<extensions><power>245</power></extensions><extensions><cadence>80</cadence></extensions><extensions><distance>1000</distance></extensions><extensions><heartrate>120</heartrate></extensions>也有其他的记录方式 如gpxtpx属性的多行记录表示:
<gpxtpx:TrackPointExtension>
<gpxtpx:hr>96</gpxtpx:hr> # 心率
<gpxtpx:cad>80</gpxtpx:cad> # 踏频
</gpxtpx:TrackPointExtension>但这种表示方法并不规范 是某些软件转换后的结果 建议使用第一种基础记录方式
若得到的gpx文件中包含gpxtpx属性的多行记录 则建议进行转换为基础记录方式 或先删除再进行添加删除属性可以调用函数 clean_data() 传参时传入"extensions" 即将所有extensions删除
也可以只删除gpxtpx:TrackPointExtension或gpxtpx:hr等属性# 注意 distance是累加数据
'''keywords_list=["heartrate","cadence","power","distance","speed"]"""将秒数转换为两位数字符串参数:st (int): 需要格式化的时间数值(0-59)返回:str: 两位数的字符串格式时间
"""
def trans_time(st):if st<10 and st>=0:st="0"+str(st)else:st=str(st)return str(st)"""找出轨迹的极值坐标点参数:gpx (list): 包含GPX原始数据的字符串列表返回:list: 包含四个极值点的二维坐标列表,格式为:[[最北点lat, lon],[最南点lat, lon],[最东点lat, lon],[最西点lat, lon]]
"""
def find_pole(gpx):south=-90north=90west=-180east=180location=[[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0]]for i in range(len(gpx)):if gpx[i].count('<trkpt'):s_list=gpx[i].split('"')lat=Decimal(s_list[1])lon=Decimal(s_list[3])if lat>south:  #最北south=latlocation[0]=[lat,lon]if lat<north:  #最南north=latlocation[1]=[lat,lon]if lon>west:  #最东west=lonlocation[2]=[lat,lon]if lon<east:  #最西east=lonlocation[3]=[lat,lon]return location"""获取GPX轨迹的起点和终点坐标参数:gpx (list): 原始GPX读取后的数据列表,每个元素为字符串行返回:list: 包含起点和终点的二维坐标列表,格式为:[[起点lat, lon],[终点lat, lon]]实现说明:1. 遍历GPX数据,记录第一个<trkpt>节点作为起点2. 持续更新最后一个<trkpt>节点作为终点3. 返回包含起点和终点的列表注意:- 当轨迹点数为0时返回[[0.0,0.0],[0.0,0.0]]- 当只有1个轨迹点时起点和终点坐标相同
"""
def find_start_end_location(gpx):location=[[0.0,0.0],[0.0,0.0]]flag = 0for i in range(len(gpx)):if gpx[i].count('<trkpt'):s_list=gpx[i].split('"')lat=Decimal(s_list[1])lon=Decimal(s_list[3])if flag == 0:location[0]=[lat,lon]flag = 1location[1]=[lat,lon]return location"""批量修改轨迹点坐标参数:gpx (list): 原始GPX数据列表,每个元素为字符串行lat_add (float): 纬度增量(十进制度数)lon_add (float): 经度增量(十进制度数)返回:list: 修改后的GPX数据列表功能说明:1. 对每个<trkpt>节点的经纬度进行加减运算2. 自动处理坐标越界:- 纬度超过±90度时反向并调整经度- 经度超过±180度时进行360度循环
"""
def change_location(gpx,lat_add,lon_add):gpx_list=[]s=''lat_add=Decimal(str(lat_add))lon_add=Decimal(str(lon_add))for i in range(len(gpx)):s=gpx[i]if gpx[i].count('<trkpt'):s_list=gpx[i].split('"')lat=Decimal(s_list[1])+lat_addlon=Decimal(s_list[3])+lon_addif lat>90:lon=lon+180lat = 180 - lat_add - s_list[1]if lat<-90:lon=lon+180lat = -(s_list[1] + 180 + lat_add)if lon>180:lon=lon-360if lon<-180:lon=lon+360s=s_list[0]+'"'+str(lat)+'"'+s_list[2]+'"'+str(lon)+'"'+s_list[4]gpx_list.append(s)return gpx_list"""随机偏移轨迹点坐标参数:gpx (list): 原始GPX数据列表返回:list: 包含随机偏移后坐标的新GPX数据列表说明:使用data_choice_2中的随机小数进行坐标偏移
"""
def change_location_random(gpx):gpx_list=[]s=''data_choice=[]for i in data_choice_2:i=Decimal(str(i))data_choice.append(i)for i in range(len(gpx)):s=gpx[i]if gpx[i].count('<trkpt'):s_list=gpx[i].split('"')lat=Decimal(s_list[1])+random.choice(data_choice) lon=Decimal(s_list[3])+random.choice(data_choice) s=s_list[0]+'"'+str(lat)+'"'+s_list[2]+'"'+str(lon)+'"'+s_list[4]gpx_list.append(s)return gpx_list"""批量修改海拔高度参数:gpx (list): 原始GPX数据列表coefficient (int): 海拔系数(乘法因子)add (int): 海拔增加值返回:list: 修改后的GPX数据列表
"""
def change_ele(gpx,coefficient,add):gpx_list=[]s=''for i in range(len(gpx)):s=gpx[i]try:            el=str((gpx[i].split("<ele>")[1]).split("</ele>")[0])            change_el=int(int(float(el))*coefficient+add)s=str(gpx[i].replace(el,str(change_el)))            except:passgpx_list.append(s)return gpx_list"""生成渐进式爬升海拔数据参数:gpx (list): 原始GPX数据列表coefficient (int): 海拔递增系数add (int): 初始海拔值返回:list: 包含渐进爬升海拔的新GPX数据列表
"""
def change_ele_climb(gpx,coefficient,add):gpx_list=[]s=''j=0for i in range(len(gpx)):s=gpx[i]try:el=str((gpx[i].split("<ele>")[1]).split("</ele>")[0])change_el=int(j*coefficient+add)j=j+1s=str(gpx[i].replace(el,str(change_el)))except:passgpx_list.append(s)return gpx_list"""生成测试用数据序列参数:n (int): 数据点数量avg (int): 平均值基准ran (int): 随机波动范围返回:list: 生成的数据序列,在avg±ran范围内波动
"""
def data_list_creat(n,avg,ran,coefficient=1):data_list=[]data_list.append(avg)data_choice=data_choice_1.copy()min_data=avg-ranmax_data=avg+ranfor i in range(n-1):  num=data_list[i]+(random.choice(data_choice))*coefficientif num < min_data:num=data_list[i]+1if num > max_data:num=data_list[i]-1data_list.append(num)            return data_list"""清理指定关键字的扩展数据参数:gpx (list): 原始GPX数据列表keywords (str): 要清理的数据关键字(如'extensions')返回:list: 清理后的GPX数据列表
"""
def clean_data(gpx,keywords):gpx_list=[]s=''clean_flag=0for i in range(len(gpx)):s=gpx[i]   if clean_flag==0:if gpx[i].count("<"+keywords+">"):clean_flag=1s_list=gpx[i].split("<"+keywords+">")if gpx[i].count("</"+keywords+">"):clean_flag=0                    s2_list = s_list[1].split("</"+keywords+">")s_list[1]=s2_list[1]s=s_list[0]+s_list[1]else:if gpx[i].count("</"+keywords+">"):clean_flag=0s_list=gpx[i].split("</"+keywords+">")s=s_list[1]else:s=''            gpx_list.append(s)return gpx_list"""生成时间序列数据参数:gpx (list): 原始GPX数据列表coefficient (int): 时间间隔系数time_str (str): 起始时间(ISO 8601格式)返回:list: 包含规范时间序列的新GPX数据
"""
def make_time(gpx,coefficient,time_str):gpx_list=[]s=''first_time=time.mktime(time.strptime(time_str, "%Y-%m-%dT%H:%M:%SZ"))now_time=first_timeflag=0for i in range(len(gpx)):s=gpx[i]if gpx[i].count('</time>') and flag==0:s='<time>'+time_str+'</time>\n'flag=1elif gpx[i].count('<trk>') and flag==0:s='<time>'+time_str+'</time>\n'+ gpx[i]flag=1elif flag==1:try:if gpx[i].count('</ele>'):now_time=now_time+1*coefficientchange_time=time.strftime("%Y-%m-%dT%H:%M:%SZ",time.localtime(now_time))  s_list=s.split("</ele>")s=s_list[0]+'</ele>\n<time>'+change_time+'</time>'+s_list[1]except:passgpx_list.append(s)return gpx_list"""生成指定类型的模拟运动随机数据参数:gpx (list): 原始GPX数据列表avg (int): 数据基准平均值ran (int): 随机波动范围keywords (str): 数据类型(如'heartrate','cadence')返回:list: 包含模拟数据的新GPX数据示例:>>> 生成平均180bpm,波动±20的心率数据make_data_random(gpx, 180, 20, 'heartrate')
"""
def make_data_random(gpx,avg,ran,keywords,coefficient=1):gpx_list=[]data_list=[]num_list=[]s=''j=0distance=0for i in range(len(gpx)):try:if gpx[i].count('</time>'):j=j+1num_list.append(i)except:passdata_list=data_list_creat(j,avg,ran,coefficient)j=0    for i in range(len(gpx)):s=gpx[i]   try:if i==num_list[j]: s_list=s.split("</time>")                if keywords == "distance":distance=distance+data_list[j]s=s_list[0]+"</time>\n<extensions><"+keywords+">"+str(distance)+"</"+keywords+"></extensions>"+s_list[1]else:s=s_list[0]+"</time>\n<extensions><"+keywords+">"+str(data_list[j])+"</"+keywords+"></extensions>"+s_list[1]j=j+1except:passgpx_list.append(s)return gpx_list"""生成指定类型的模拟运动随机数据参数:gpx (list): 原始GPX数据列表data_list (list): 数据列表keywords (str): 数据类型(如'heartrate','cadence')返回:list: 包含模拟数据的新GPX数据示例:make_data_list(gpx, list, 'heartrate')
"""
def make_data_list(gpx,data_list,keywords):gpx_list=[]num_list=[]s=''j=0for i in range(len(gpx)):try:if gpx[i].count('</time>'):j=j+1num_list.append(i)except:passj=0    data=0for i in range(len(gpx)):s=gpx[i]   try:if i==num_list[j]: try:data=data_list[j]                                except:data=datatry:float(data)        except:data = 0if not data or str(data)=="nan":data=0s_list=s.split("</time>")s=s_list[0]+"</time>\n<extensions><"+keywords+">"+str(data)+"</"+keywords+"></extensions>"+s_list[1]j=j+1except:passgpx_list.append(s)return gpx_list"""按系数调整指定类型的数据值参数:gpx (list): 原始GPX数据列表coefficient (float): 调整系数(如0.8表示打八折)keywords (str): 要修改的数据类型返回:list: 调整后的GPX数据列表典型应用:功率打折、心率平滑等场景
"""
def change_data(gpx,coefficient,keywords):gpx_list=[]s=''for i in range(len(gpx)):s=gpx[i]try:heart=str((gpx[i].split("<"+keywords+">")[1]).split("</"+keywords+">")[0])change=int(int(heart)*coefficient)                s=str(gpx[i].replace(heart,str(change)))except:passgpx_list.append(s)return gpx_list"""调整时间轴并添加时间偏移参数:gpx (list): 原始GPX数据列表coefficient (float): 时间缩放系数(1.0保持原速)time_add (int): 时间偏移量(秒)返回:list: 调整时间后的新GPX数据算法说明:1. 保持首个时间点不变2. 后续时间点按系数缩放后偏移3. 自动处理跨日时间戳
"""
def change_time(gpx,coefficient,time_add):gpx_list=[]s=''first_time=0for i in range(len(gpx)):s=gpx[i]try:ti=str((gpx[i].split("<time>")[1]).split("</time>")[0])now_time = time.mktime(time.strptime(ti, "%Y-%m-%dT%H:%M:%SZ"))if first_time==0:first_time=now_timechange_time=now_time+time_addelse:change_time=((now_time-first_time)*coefficient)+first_time+time_addchange_time=time.strftime("%Y-%m-%dT%H:%M:%SZ",time.localtime(change_time))  s=str(gpx[i].replace(ti,str(change_time)))except:passgpx_list.append(s)return gpx_list"""复制轨迹段并生成连续轨迹参数:gpx (list): 原始GPX数据列表n (int): 复制次数random_flag (int): 是否启用随机偏移(1启用/0禁用)返回:list: 包含重复轨迹段的新GPX文件说明:自动处理时间连续性和坐标偏移问题
"""
def copy_gpx(gpx,n,random_flag):start_end_location=find_start_end_location(gpx)start_lat = float(start_end_location[0][0])start_lon = float(start_end_location[0][1])end_lat = float(start_end_location[1][0]) end_lon = float(start_end_location[1][1])shift_time = 0delta_lat = (end_lat - start_lat) * 111delta_lon = (end_lon - start_lon) * 111 * np.cos(np.radians(end_lat))distance_km = np.sqrt(delta_lat**2 + delta_lon**2)# 计算延迟时间(每公里2分钟→120秒)shift_time = int(distance_km * 120)gpx_list=[]gpx_trkpt_list=[]gpx_trkpt_list_change=[]s=''first_time=0last_time=0single_time=0flag=0for i in range(len(gpx)):s=gpx[i]try:ti=str((gpx[i].split("<time>")[1]).split("</time>")[0])now_time = time.mktime(time.strptime(ti, "%Y-%m-%dT%H:%M:%SZ"))if first_time==0:first_time=now_timeelse:last_time=now_timeexcept:passif flag==0:gpx_list.append(s)if gpx[i].count('<trkseg>'):flag=1else:if gpx[i].count('</trkseg>'):s=gpx[i].split("</trkseg>")[0]gpx_trkpt_list.append(s)breakelse:gpx_trkpt_list.append(s)single_time=last_time-first_time+shift_timefor i in range(n):gpx_trkpt_list_change=change_time(gpx_trkpt_list,1,single_time*i+1)if random_flag==1:gpx_trkpt_list_change=change_location_random(gpx_trkpt_list_change)gpx_list=gpx_list+gpx_trkpt_list_changegpx_list.append("\n</trkseg>\n</trk>\n</gpx>\n")return gpx_list"""拼接轨迹段并生成连续轨迹参数:gpx1/gpx2: 原始GPX数据列表返回:list: 包含重复轨迹段的新GPX文件说明:自动处理时间连续性和坐标偏移问题
"""
def splice_gpx(gpx1,gpx2):start_end_location1=find_start_end_location(gpx1)end_lat1 = float(start_end_location1[1][0]) end_lon1 = float(start_end_location1[1][1])start_end_location2=find_start_end_location(gpx2)start_lat2 = float(start_end_location2[0][0])start_lon2 = float(start_end_location2[0][1])shift_time = 0delta_lat = (start_lat2 - end_lat1) * 111delta_lon = (start_lon2 - end_lon1) * 111 * np.cos(np.radians(start_lat2))distance_km = np.sqrt(delta_lat**2 + delta_lon**2)# 计算延迟时间(每公里2分钟→120秒)shift_time = int(distance_km * 120)single_time=0gpx_list=[]gpx_list1=[]gpx_list2=[]gpx_trkpt_list1=[]gpx_trkpt_list2=[]s=''first_time1=0last_time1=0   flag=0for i in range(len(gpx1)):s=gpx1[i]try:ti=str((gpx1[i].split("<time>")[1]).split("</time>")[0])now_time = time.mktime(time.strptime(ti, "%Y-%m-%dT%H:%M:%SZ"))if first_time1==0:first_time1=now_timeelse:last_time1=now_timeexcept:passif flag==0:gpx_list1.append(s)if gpx1[i].count('<trkseg>'):flag=1else:if gpx1[i].count('</trkseg>'):s=gpx1[i].split("</trkseg>")[0]gpx_trkpt_list1.append(s)breakelse:gpx_trkpt_list1.append(s)first_time2=0last_time2=0s=''flag=0for i in range(len(gpx2)):s=gpx2[i]try:ti=str((gpx2[i].split("<time>")[1]).split("</time>")[0])now_time = time.mktime(time.strptime(ti, "%Y-%m-%dT%H:%M:%SZ"))if first_time2==0:first_time2=now_timeelse:last_time2=now_timeexcept:passif flag==0:gpx_list2.append(s)if gpx2[i].count('<trkseg>'):flag=1else:if gpx2[i].count('</trkseg>'):s=gpx2[i].split("</trkseg>")[0]gpx_trkpt_list2.append(s)breakelse:gpx_trkpt_list2.append(s)single_time=last_time1 - first_time2 +shift_timegpx_trkpt_list2_change=change_time(gpx_trkpt_list2,1,single_time+1)gpx_list=gpx_list1+gpx_trkpt_list1+gpx_trkpt_list2_changegpx_list.append("\n</trkseg>\n</trk>\n</gpx>\n")return gpx_list# 更改gpx类型
def change_gpx_flag(gpx,gpx_flag):gpx_list = []flag = 0for i in gpx:        if "<metadata>" in i:flag=1if flag:gpx_list.append(i)gpx_list[0] = gpx_flag + gpx_list[0].split("<metadata>")[-1]return gpx_list# 保存文件
def save_lines(lines,path):try:f=open(path, 'w', encoding="utf-8")except:f=open(path, 'a', encoding="utf-8")for i in lines:f.write(i)f.close()'''
读取GPX文件并解析为结构化数据字典参数:gpx (list): GPX文件内容列表,每行字符串为一个元素 需要读取gpx文件后 导入readlines返回:dict: 包含以下键值的结构化数据字典:num (list): 数据点索引列表time (list): ISO 8601时间戳列表lat (list): 纬度值列表(浮点数)lon (list): 经度值列表(浮点数)ele (list): 海拔高度列表(浮点数)key (dict): 扩展数据字典,包含心率/踏频等指标列表说明:1. 自动跳过无法解析的数据行2. 时间戳格式为YYYY-MM-DDTHH:MM:SSZ3. 无效数据点用None填充
'''
def read_gpx(gpx):first_time=0first_str_time=""trkpt_flag=0j=0num_list=[0]time_list=[None]ele_list=[None]lat_list=[None]lon_list=[None]key_list=[]gpx_dict={}key_dict={}for i in keywords_list:key_list.append([None])for i in range(len(gpx)):        try:ti=str((gpx[i].split("<time>")[1]).split("</time>")[0])now_time = time.mktime(time.strptime(ti, "%Y-%m-%dT%H:%M:%SZ"))first_time=now_time                first_str_time=time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(first_time))  breakexcept:passtime_list[0]=first_str_timefor i in range(len(gpx)):if gpx[i].count('<trkpt'):trkpt_flag=1      j=j+1num_list.append(j)time_list.append(j)ele_list.append(j)lat_list.append(j)lon_list.append(j)            for k in range(len(keywords_list)):key_list[k].append(0)if trkpt_flag==1:try:ti=str((gpx[i].split("<time>")[1]).split("</time>")[0])now_time = time.mktime(time.strptime(ti, "%Y-%m-%dT%H:%M:%SZ"))now_str_time=time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(now_time))  time_list[j]=now_str_timeexcept:passif gpx[i].count('<trkpt'):lat_list[j]=float(gpx[i].split('"')[1])lon_list[j]=float(gpx[i].split('"')[3])try:ele_list[j]=float(str((gpx[i].split("<ele>")[1]).split("</ele>")[0]))except:passfor k in range(len(keywords_list)):           try:dat=str((gpx[i].split("<"+keywords_list[k]+">")[1]).split("</"+keywords_list[k]+">")[0])key_list[k][j]=float(dat)except:passif gpx[i].count('</trkpt>'):trkpt_flag=0if gpx[i].count('</trkseg>'):breakgpx_dict["num"]=num_list    gpx_dict["time"]=time_list       gpx_dict["lat"]=lat_listgpx_dict["lon"]=lon_listgpx_dict["ele"]=ele_list for k in range(len(keywords_list)):key_dict[keywords_list[k]]=key_list[k]gpx_dict["key"]=key_dictreturn gpx_dict'''
计算轨迹点移动速度
计算的速度与距离 与原本GPX中的distance和speed无关参数:gpx_dict (dict): 包含GPX数据的字典,需包含"time","lat","lon","ele"use_ele_flag (bool): 是否考虑海拔高度影响use_curvature_flag (bool): 是否考虑曲率影响内部参数:time_list (list): 时间戳列表(ISO 8601格式)lat_list (list): 纬度值列表lon_list (list): 经度值列表ele_list (list): 海拔高度列表key_dict (dict): 扩展数据字典,包含心率/踏频等指标列表返回:pandas.DataFrame: 包含以下列的数据框:time: 原始时间戳lat/lon/ele: 原始坐标数据arclength: 相邻点间弧长(米)velocity: 计算速度(km/h)算法说明:1. 使用Haversine公式计算地球表面弧长2. 考虑海拔高度对地球半径的影响(R = 6371393 + 当前海拔)3. 自动处理时间戳相同/乱序的特殊情况4. 速度单位转换为km/h(1m/s = 3.6km/h)注意:需安装pandas和numpy库才能正常使用
''' 
def calculate_velocity(gpx_dict,use_ele_flag = True,use_curvature_flag = False):time_list=gpx_dict["time"]lat_list=gpx_dict["lat"]lon_list=gpx_dict["lon"]ele_list=gpx_dict["ele"]key_dict=gpx_dict["key"]key_list = []for i in keywords_list:key_list.append(key_dict[i])if len(time_list)==len(lat_list) and len(time_list)==len(lon_list) and len(time_list)==len(ele_list):passelse:return if len(key_list)==5:df = DataFrame({'time': time_list, 'lat': lat_list, 'lon': lon_list, 'ele': ele_list, keywords_list[0]:key_list[0],keywords_list[1]:key_list[1],keywords_list[2]:key_list[2],keywords_list[3]:key_list[3],keywords_list[4]:key_list[4]})else:df = DataFrame({'time': time_list, 'lat': lat_list, 'lon': lon_list, 'ele': ele_list})columns = df.columns.tolist()if use_curvature_flag:df['r'] = 6371393+df['ele'] # radius of the Earth in metersdf['theta'] = np.deg2rad(df['lon'])df['phi'] = np.deg2rad(df['lat'])df['theta2']=df['theta'].shift()df['phi2']=df['phi'].shift()try:cos_angle = np.cos(df['phi']) * np.cos(df['phi2']) * np.cos(df['theta2']-df['theta']) + np.sin(df['phi'])*np.sin(df['phi2'])df['arclength'] = df['r'] * np.arccos(np.clip(cos_angle, -1.0, 1.0))except Exception as e:print(f"计算异常:{e}")df['arclength'] = df['r'] * np.sqrt((df['theta2']-df['theta'])**2 * np.cos(df['phi'])**2+ (df['phi2']-df['phi'])**2)   else:# 不考虑曲率 考虑ele的变化 经纬度直接转距离 纬度1度≈111km 经度1度≈111km*cos(纬度)# 删除地球半径和球面坐标转换df['lat_diff'] = df['lat'].diff() * 111000  # 纬度差转米(1度≈111km)df['lon_diff'] = df['lon'].diff() * 111000 * np.cos(np.deg2rad(df['lat']))  # 经度差转米# 计算平面距离(勾股定理)df['arclength'] = np.sqrt(df['lat_diff']**2 + df['lon_diff']**2)if use_ele_flag:df['ele_diff'] = df['ele'].diff()  # 获取海拔变化量(米)df['arclength'] = np.sqrt(df['arclength']**2 + df['ele_diff']**2)elapsed_time_list=[]stamp_list=[]velocity_list=[]stamp_same_list=[]same_flag=0enable_flag=0exit_same_list=[]for i in range(len(time_list)):try:stamp_list.append(time.mktime(time.strptime(time_list[i], "%Y-%m-%d %H:%M:%S")))enable_flag=1if i>0 and enable_flag==1:if stamp_list[i]>stamp_list[i-1]:elapsed_time_list.append(stamp_list[i]-stamp_list[i-1]) if same_flag==1:same_flag=0exit_same_list.append(i)else:if same_flag==0:stamp_same_list.append(i)same_flag=1elapsed_time_list.append(0.0)else:elapsed_time_list.append(None)except:if enable_flag==0:elapsed_time_list.append(None)else:if same_flag==0:stamp_same_list.append(i)same_flag=1elapsed_time_list.append(0.0)if len(stamp_same_list)>len(exit_same_list):exit_same_list.append(0)  j=0displacement=0.0displacement_time=0.0end_displacement=0.0same_flag=0end_elapsed_time=0.0stamp_same_in_1st_flag=0if len(stamp_same_list)==0:for i in range(len(elapsed_time_list)):try:try:velocity_list.append((df['arclength'][i]+df['arclength'][i+1])/ (elapsed_time_list[i]+elapsed_time_list[i+1]))  # km/hexcept:velocity_list.append(df['arclength'][i]/ elapsed_time_list[i])  # m/sexcept:velocity_list.append(None)else:  for i in range(len(elapsed_time_list)):try:if i>0:try:if i==stamp_same_list[j]-1:same_flag=1if stamp_same_list[0]==1 and stamp_same_in_1st_flag==0:stamp_same_in_1st_flag=1same_flag=1except:passif same_flag==0:try:velocity_list.append((df['arclength'][i]+df['arclength'][i+1])/ (elapsed_time_list[i]+elapsed_time_list[i+1]))  # m/sexcept:velocity_list.append(df['arclength'][i]/ elapsed_time_list[i])  # m/selse:   try:displacement=displacement+df['arclength'][i]except:displacement=displacement+0.0try:displacement_time=displacement_time+elapsed_time_list[i]except:displacement_time=displacement_time+0.0if j==len(stamp_same_list)-1 and exit_same_list[j]==0:if end_elapsed_time==0.0:end_elapsed_time=elapsed_time_list[i]                                end_displacement=end_displacement+df['arclength'][i]if end_elapsed_time>0.0:velocity_list.append(end_displacement/end_elapsed_time)else:velocity_list.append(end_displacement/1.0)else:velocity_list.append(None)if i==exit_same_list[j]: avg_velocity=displacement/displacement_timefor k in range(stamp_same_list[j]-1,i+1):velocity_list[k]=avg_velocity       j=j+1try:if elapsed_time_list[j]-end_elapsed_time[j-1]<=1:passelse:same_flag=0displacement=0.0displacement_time=0.0except:same_flag=0displacement=0.0displacement_time=0.0else:velocity_list.append(None)except:velocity_list.append(None)df['elapsed_time'] = elapsed_time_listdf['velocity'] = velocity_list  # m/sdf['velocity'] = 3.6*df['velocity'] # km/hdf = df[columns + ['arclength']+['velocity']]return df"""将GPX数据转换为SRT字幕格式 直接转 不进行速度计算等参数:gpx (list): 包含GPX原始数据的字符串列表 读取gpx文件后的readlines返回:list: 包含完整SRT字幕内容的列表,每个元素代表一个字幕段落
"""    
def gpx_to_srt(gpx):     srt_list=[]first_time=0srt_time=0srt=""j=1trkpt_flag=0trkpt_first_flag=0for i in range(len(gpx)):        try:ti=str((gpx[i].split("<time>")[1]).split("</time>")[0])now_time = time.mktime(time.strptime(ti, "%Y-%m-%dT%H:%M:%SZ"))first_time=now_time                srt_time=0str_now_time=time.strftime("<i>%Y-%m-%d %H:%M:%S</i>\n",time.localtime(now_time))  srt=str(j)+"\n00:00:00,000 --> 00:00:01,000\n"+str_now_time+"<u>BEGIN</u>\n\n"srt_list.append(srt)breakexcept:passsrt=""srt_str_time=""last_distance=0srt_time=0last_srt_time=0for i in range(len(gpx)):if gpx[i].count('<trkpt'):trkpt_flag=1trkpt_first_flag=1j=j+1if trkpt_flag==1:try:ti=str((gpx[i].split("<time>")[1]).split("</time>")[0])now_time = time.mktime(time.strptime(ti, "%Y-%m-%dT%H:%M:%SZ"))                srt_time=now_time-first_timeh=int(srt_time/3600)m=int(srt_time/60)s=int(srt_time%60)s2=s+1h=trans_time(h)m=trans_time(m) s=trans_time(s)s2=trans_time(s2)srt_str_time=str(h+":"+m+":"+s+",000 --> "+h+":"+m+":"+s2+",000\n")str_now_time=time.strftime("<i>%Y-%m-%d %H:%M:%S</i>\n",time.localtime(now_time))  srt=srt+str_now_timeexcept:passif gpx[i].count('<trkpt'):srt=srt+"lat:"+gpx[i].split('"')[1]+" lon:"+gpx[i].split('"')[3]+"\n"try:el=str((gpx[i].split("<ele>")[1]).split("</ele>")[0]) srt=srt+"ele:"+el+"\n"except:passfor key in keywords_list:                try:dat=str((gpx[i].split("<"+key+">")[1]).split("</"+key+">")[0]) srt=srt+"<i>"+key+":"+dat+"</i>\n"if key=="distance":    velocity_time=srt_time-last_srt_timevelocity=(int(dat)-last_distance)/velocity_time*3.6                        srt=srt+"<b>"+"velocity"+":"+str(velocity)+"</b>\n"                        except:passtry:dat=str((gpx[i].split("<velocity>")[1]).split("</velocity>")[0]) srt=srt+"<b>"+"velocity"+":"+dat+"</b>\n"except:passtry:dat=str((gpx[i].split("<arclength>")[1]).split("</arclength>")[0]) srt=srt+"<i>"+"arclength"+":"+dat+"</i>\n"except:passif gpx[i].count('</trkpt>'):trkpt_flag=0if trkpt_first_flag==1 and trkpt_flag==0:if not srt=="":                last_srt_time=int(srt_time)last_distance=int(dat)srt=str(j)+"\n"+srt_str_time+srt+"\n"srt_list.append(srt)srt=""if gpx[i].count('</trkseg>'):j=j+1trkpt_first_flag=2if trkpt_first_flag==2:h=int((last_srt_time+1)/3600)m=int((last_srt_time+1)/60)s=int((last_srt_time+1)%60)s2=s+1h=trans_time(h)m=trans_time(m) s=trans_time(s)s2=trans_time(s2)srt_str_time=str(h+":"+m+":"+s+",000 --> "+h+":"+m+":"+s2+",000\n")srt=str(j)+"\n"+srt_str_time+"<u>END</u>\n"+"\n"srt_list.append(srt)srt=""breakreturn srt_list"""将包含速度计算的DataFrame转换为SRT字幕格式 需要首先经过速度计算得到DataFrame的GPX数据参数:df (DataFrame): 包含轨迹数据的DataFrame,需包含以下列:time: 时间戳(与原函数相同格式)lat/lon/ele: 坐标和海拔heartrate/cadence/power/velocity: 传感器数据返回:list: 包含完整SRT字幕内容的列表
"""
def gpx_df_to_srt(df):srt_list = []if df.empty:return srt_listtotal_arclength = 0.0# 初始化首条字幕first_time = df.iloc[0]['time']srt_list.append(f"1\n00:00:00,000 --> 00:00:01,000\n<i>{first_time}</i>\n<u>BEGIN</u>\n\n")# 生成数据点字幕for i in range(len(df)):row = df.iloc[i]        # 时间计算start_sec = iend_sec = i + 1time_str = f"{start_sec//3600:02}:{(start_sec%3600)//60:02}:{start_sec%60:02},000 --> " \f"{end_sec//3600:02}:{(end_sec%3600)//60:02}:{end_sec%60:02},000\n"# 构建内容content = f"<i>{row['time']}</i>\n" \f"lat:{row['lat']:.7f} lon:{row['lon']:.7f}\n" \f"ele:{row['ele']:.1f}\n" \f"<u>total arclength:{total_arclength/1000:.2f}</u>\n"# 添加传感器数据for key in keywords_list:if key in row and notnull(row[key]):content += f"<i>{key}:{row[key]:.0f}</i>\n"# 添加速度数据if 'velocity' in row and notnull(row['velocity']):content += f"<b>velocity:{row['velocity']:.1f}</b>\n"# 总距离计算if 'arclength' in row and notnull(row['arclength']):total_arclength += row['arclength']# 构建字幕srt_list.append(f"{i+2}\n{time_str}{content}\n")# 添加结束标记srt_list.append(f"{len(df)+2}\n{time_str}<u>END</u>\n\n")return srt_listdef demo():passpath="C:/Users/ZHOU/Desktop/1.gpx"save_path="C:/Users/ZHOU/Desktop/2.gpx"f=open(path, 'r', encoding="utf-8")gpx=f.readlines()f.close()    gpx=change_location(gpx,7.867624,0.011124)#gpx=make_time(gpx,1,"2025-08-27T15:05:04Z")gpx=change_ele(gpx,1,-185)gpx=change_ele_climb(gpx,1,100)gpx=make_data_random(gpx,160,20,"heartrate")gpx=make_data_random(gpx,250,50,"power")gpx=clean_data(gpx,"extensions")gpx=make_data_random(gpx,90,20,"cadence")gpx=change_data(gpx,1.5,"power")gpx = change_gpx_flag(gpx,COROS_GPX_FLAG)gpx=change_time(gpx,1,-1800*24*8)gpx=change_location_random(gpx)gpx=copy_gpx(gpx,3,1)gpx_dict=read_gpx(gpx)#print(gpx_dict)df=calculate_velocity(gpx_dict)print(df)srt_list=gpx_df_to_srt(df)gpx=make_data_list(gpx,df["velocity"],"velocity")save_lines(srt_list,"C:/Users/ZHOU/Desktop/1.srt")save_lines(gpx,save_path)f=open("C:/Users/ZHOU/Desktop/a.gpx", 'r', encoding="utf-8")gpx1=f.readlines()f.close()    f=open("C:/Users/ZHOU/Desktop/b.gpx", 'r', encoding="utf-8")gpx2=f.readlines()f.close()    gpx=splice_gpx(gpx1,gpx2)gpx = change_time(gpx,1,10000)gpx = change_gpx_flag(gpx,COROS_GPX_FLAG)save_lines(gpx,"C:/Users/ZHOU/Desktop/c.gpx")

GUI界面

# -*- coding: utf-8 -*-
"""
Created on Mon Jun  5 10:29:48 2023@author: ZHOU
"""import gpx_function as gf
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import osclass GPXEditor:def __init__(self, root):self.root = rootself.root.title("GPX文件编辑器")self.root.geometry("900x750")self.center_window(self.root)self.gpx_file = Noneself.gpx_data = Noneself.secondary_gpx_file = None  # 新增次级文件属性self.secondary_gpx_data = Noneself.create_widgets()def center_window(self, window):"""使窗口居中显示"""window.update_idletasks()width = window.winfo_width()height = window.winfo_height()x = (window.winfo_screenwidth() // 2) - (width // 2)y = (window.winfo_screenheight() // 2) - (height // 2)window.geometry(f"{width}x{height}+{x}+{y}")def create_widgets(self):# 文件选择区域file_frame = tk.LabelFrame(self.root, text="文件操作", padx=10, pady=10)file_frame.pack(fill="x", padx=10, pady=5)tk.Button(file_frame, text="选择GPX文件", command=self.select_gpx_file).grid(row=0, column=0, padx=5, pady=5)tk.Button(file_frame, text="取消选择", command=self.clear_gpx_file, width=10).grid(row=0, column=1, padx=5, pady=5)self.file_label = tk.Label(file_frame, text="未选择文件")self.file_label.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky="w")self.full_path_label = tk.Label(file_frame, text="", fg="gray", font=("Arial", 8))self.full_path_label.grid(row=2, column=0, columnspan=2, padx=5, pady=2, sticky="w")# 输出路径设置output_frame = tk.LabelFrame(self.root, text="输出设置", padx=10, pady=10)output_frame.pack(fill="x", padx=10, pady=5)tk.Label(output_frame, text="输出文件名:").grid(row=0, column=0, padx=5, pady=5)self.output_entry = tk.Entry(output_frame, width=50)self.output_entry.grid(row=0, column=1, padx=5, pady=5)tk.Button(output_frame, text="浏览", command=self.select_output_file).grid(row=0, column=2, padx=5, pady=5)tk.Label(output_frame, text="SRT文件名:").grid(row=1, column=0, padx=5, pady=5)self.srt_entry = tk.Entry(output_frame, width=50)self.srt_entry.grid(row=1, column=1, padx=5, pady=5)tk.Button(output_frame, text="浏览", command=self.select_srt_file).grid(row=1, column=2, padx=5, pady=5)# 功能按钮区域 - 重新分组main_func_frame = tk.Frame(self.root)main_func_frame.pack(fill="both", expand=True, padx=10, pady=5)# 第一组:扩展数据操作ext_data_frame = tk.LabelFrame(main_func_frame, text="扩展数据操作", padx=10, pady=10)ext_data_frame.pack(fill="both", expand=True, side="left", padx=5, pady=5)ext_buttons = [("生成心率数据", lambda: self.make_data_random("心率")),("修改心率数据", lambda: self.change_data_type("心率")),("生成踏频数据", lambda: self.make_data_random("踏频")),("修改踏频数据", lambda: self.change_data_type("踏频")),("生成功率数据", lambda: self.make_data_random("功率")),("修改功率数据", lambda: self.change_data_type("功率")),("生成距离数据", lambda: self.make_data_random("距离")),("修改距离数据", lambda: self.change_data_type("距离")),("生成速度数据", lambda: self.make_data_random("速度")),("修改速度数据", lambda: self.change_data_type("速度")),("清理扩展数据", self.clean_data)]for i, (text, command) in enumerate(ext_buttons):btn = tk.Button(ext_data_frame, text=text, command=command, width=15)btn.grid(row=i//2, column=i%2, padx=5, pady=5, sticky="ew")# 自定义关键词功能custom_frame = tk.Frame(ext_data_frame)custom_frame.grid(row=6, column=0, columnspan=2, sticky="ew", padx=5, pady=5)tk.Label(custom_frame, text="自定义关键词:").pack(side="left")self.custom_keyword_entry = tk.Entry(custom_frame, width=10)self.custom_keyword_entry.pack(side="left", padx=2)tk.Button(custom_frame, text="修改数据", command=self.change_custom_data, width=8).pack(side="left", padx=2)tk.Button(custom_frame, text="生成数据", command=self.make_custom_data, width=8).pack(side="left", padx=2)# 第二组:基础数据操作base_data_frame = tk.LabelFrame(main_func_frame, text="基础数据操作", padx=10, pady=10)base_data_frame.pack(fill="both", expand=True, side="left", padx=5, pady=5)base_buttons = [("修改坐标", self.change_location),("随机偏移坐标", self.change_location_random),("修改海拔", self.change_ele),("渐进爬升", self.change_ele_climb),("生成时间序列", self.make_time),("调整时间轴", self.change_time),("读取GPX数据", self.read_gpx),  # 移到基础数据操作("计算速度", self.calculate_velocity),  # 移到基础数据操作("整合数据", self.integrate_data)  # 新增整合数据按钮]for i, (text, command) in enumerate(base_buttons):btn = tk.Button(base_data_frame, text=text, command=command, width=15)btn.grid(row=i//2, column=i%2, padx=5, pady=5, sticky="ew")# 第三组:文件操作file_ops_frame = tk.LabelFrame(main_func_frame, text="文件操作", padx=10, pady=10)file_ops_frame.pack(fill="both", expand=True, side="left", padx=5, pady=5)# GPX标志选择gpx_flag_frame = tk.Frame(file_ops_frame)gpx_flag_frame.pack(fill="x", pady=5)tk.Label(gpx_flag_frame, text="GPX类型:").pack(side="left")self.gpx_flag_var = tk.StringVar(value="COROS")self.gpx_flag_combo = ttk.Combobox(gpx_flag_frame, textvariable=self.gpx_flag_var, values=list(gf.GPX_FLAGS.keys()), width=8)self.gpx_flag_combo.pack(side="left", padx=2)tk.Button(gpx_flag_frame, text="修改标志", command=self.change_gpx_flag, width=10).pack(side="left")# 文件操作按钮file_ops_buttons = [("生成SRT", self.generate_srt),("生成最终SRT", self.generate_final_srt),  # 新增生成最终SRT按钮("复制轨迹段", self.copy_gpx),("保存文件", self.save_files)]for i, (text, command) in enumerate(file_ops_buttons):btn = tk.Button(file_ops_frame, text=text, command=command, width=15)btn.pack(pady=3)# 次级文件选择和拼接功能secondary_frame = tk.Frame(file_ops_frame)secondary_frame.pack(fill="x", pady=5)tk.Button(secondary_frame, text="选择次级GPX文件", command=self.select_secondary_file, width=15).pack(pady=3)tk.Button(secondary_frame, text="取消选择次级文件", command=self.clear_secondary_file, width=15).pack(pady=3)self.secondary_file_label = tk.Label(secondary_frame, text="未选择次级文件", fg="gray")self.secondary_file_label.pack(pady=2)self.secondary_full_path_label = tk.Label(secondary_frame, text="", fg="gray", font=("Arial", 7))self.secondary_full_path_label.pack(pady=1)tk.Button(secondary_frame, text="拼接文件", command=self.splice_files, width=15).pack(pady=3)# 状态显示self.status_label = tk.Label(self.root, text="就绪", bd=1, relief=tk.SUNKEN, anchor=tk.W)self.status_label.pack(side=tk.BOTTOM, fill=tk.X)def integrate_data(self):"""整合数据:先计算速度,再将速度和弧长数据写入GPX"""if not self.gpx_data:messagebox.showwarning("警告", "请先选择并加载GPX文件")returntry:# 先进行速度计算gpx_dict = gf.read_gpx(self.gpx_data)df = gf.calculate_velocity(gpx_dict)# 将速度数据写入GPXself.gpx_data = gf.make_data_list(self.gpx_data, df["velocity"], "velocity")# 将弧长数据写入GPX(如果存在arclength列)if "arclength" in df.columns:self.gpx_data = gf.make_data_list(self.gpx_data, df["arclength"], "arclength")messagebox.showinfo("整合数据", "数据整合完成!已将速度和弧长数据写入GPX文件")self.status_label.config(text="数据整合完成")except Exception as e:messagebox.showerror("错误", f"数据整合失败: {e}")self.status_label.config(text="数据整合失败")def generate_final_srt(self):if not self.gpx_data:messagebox.showwarning("警告", "请先选择并加载GPX文件")returntry:# 先进行速度计算gpx_dict = gf.read_gpx(self.gpx_data)df = gf.calculate_velocity(gpx_dict)# 使用速度计算结果生成SRTsrt_list = gf.gpx_df_to_srt(df)srt_path = self.srt_entry.get()if srt_path:gf.save_lines(srt_list, srt_path)messagebox.showinfo("最终SRT生成", f"最终SRT文件已保存: {srt_path}\n包含速度计算结果")self.status_label.config(text="最终SRT生成完成")else:messagebox.showwarning("警告", "请先设置SRT输出路径")except Exception as e:messagebox.showerror("错误", f"最终SRT生成失败: {e}")self.status_label.config(text="最终SRT生成失败")def change_data_type(self, data_type):mapping = {"心率": "heartrate", "踏频": "cadence", "功率": "power", "距离": "distance", "速度": "speed"}keyword = mapping[data_type]self.show_param_dialog(f"修改{data_type}数据", [("调整系数", "1.0")], lambda params: gf.change_data(self.gpx_data, float(params[0]), keyword))def change_custom_data(self):keyword = self.custom_keyword_entry.get().strip()if not keyword:messagebox.showwarning("警告", "请输入自定义关键词")returnself.show_param_dialog(f"修改{keyword}数据", [("系数", "1")], lambda params: gf.change_data(self.gpx_data, float(params[0]), keyword))def make_custom_data(self):keyword = self.custom_keyword_entry.get().strip()if not keyword:messagebox.showwarning("警告", "请输入自定义关键词")returnself.show_param_dialog(f"生成{keyword}数据", [("平均值", "100"), ("波动范围", "20")], lambda params: gf.make_data_random(self.gpx_data, int(params[0]), int(params[1]), keyword))def change_gpx_flag(self):if not self.gpx_data:messagebox.showwarning("警告", "请先选择并加载GPX文件")returnselected_flag = self.gpx_flag_var.get()if selected_flag in gf.gf.GPX_FLAGS:self.gpx_data = gf.change_gpx_flag(self.gpx_data, gf.gf.GPX_FLAGS[selected_flag])self.status_label.config(text=f"GPX标志已修改为: {selected_flag}")else:messagebox.showerror("错误", "选择的GPX类型不存在")def clear_gpx_file(self):self.gpx_file = Noneself.gpx_data = Noneself.file_label.config(text="未选择文件")self.full_path_label.config(text="")self.output_entry.delete(0, tk.END)self.srt_entry.delete(0, tk.END)self.status_label.config(text="已取消文件选择")def select_gpx_file(self):file_path = filedialog.askopenfilename(title="选择GPX文件",filetypes=[("GPX files", "*.gpx"), ("All files", "*.*")])if file_path:self.gpx_file = file_pathself.file_label.config(text=os.path.basename(file_path))self.full_path_label.config(text=file_path)# 自动设置输出文件名,保持与选择文件相同的目录base_name = os.path.splitext(os.path.basename(file_path))[0]file_dir = os.path.dirname(file_path)self.output_entry.delete(0, tk.END)self.output_entry.insert(0, os.path.join(file_dir, f"{base_name}_new.gpx"))self.srt_entry.delete(0, tk.END)self.srt_entry.insert(0, os.path.join(file_dir, f"{base_name}_new.srt"))self.load_gpx_data()def select_output_file(self):if self.gpx_file:initial_file = self.output_entry.get() or "output.gpx"file_path = filedialog.asksaveasfilename(title="选择输出GPX文件",initialfile=initial_file,filetypes=[("GPX files", "*.gpx"), ("All files", "*.*")])if file_path:self.output_entry.delete(0, tk.END)self.output_entry.insert(0, file_path)def select_secondary_file(self):file_path = filedialog.askopenfilename(title="选择次级GPX文件",filetypes=[("GPX files", "*.gpx"), ("All files", "*.*")])if file_path:self.secondary_gpx_file = file_pathself.secondary_file_label.config(text=os.path.basename(file_path))self.secondary_full_path_label.config(text=file_path)self.status_label.config(text="次级文件选择成功")def clear_secondary_file(self):self.secondary_gpx_file = Noneself.secondary_gpx_data = Noneself.secondary_file_label.config(text="未选择次级文件")self.secondary_full_path_label.config(text="")self.status_label.config(text="已取消次级文件选择")def splice_files(self):if not self.gpx_data:messagebox.showwarning("警告", "请先选择并加载主GPX文件")returnif not self.secondary_gpx_file:messagebox.showwarning("警告", "请先选择次级GPX文件")returntry:# 加载次级文件数据with open(self.secondary_gpx_file, 'r', encoding="utf-8") as f:secondary_data = f.readlines()# 调用拼接函数self.gpx_data = gf.splice_gpx(self.gpx_data, secondary_data)self.status_label.config(text="文件拼接完成")messagebox.showinfo("成功", "GPX文件拼接完成")except Exception as e:messagebox.showerror("错误", f"文件拼接失败: {e}")self.status_label.config(text="文件拼接失败")def select_srt_file(self):if self.gpx_file:initial_file = self.srt_entry.get() or "output.srt"file_path = filedialog.asksaveasfilename(title="选择输出SRT文件",initialfile=initial_file,filetypes=[("SRT files", "*.srt"), ("All files", "*.*")])if file_path:self.srt_entry.delete(0, tk.END)self.srt_entry.insert(0, file_path)def load_gpx_data(self):if self.gpx_file:try:with open(self.gpx_file, 'r', encoding="utf-8") as f:self.gpx_data = f.readlines()self.status_label.config(text="GPX文件加载成功")except Exception as e:messagebox.showerror("错误", f"读取文件失败: {e}")self.status_label.config(text="文件读取失败")def change_location(self):self.show_param_dialog("修改坐标", [("纬度增量", "0.0"), ("经度增量", "0.0")], lambda params: gf.change_location(self.gpx_data, float(params[0]), float(params[1])))def change_location_random(self):self.gpx_data = gf.change_location_random(self.gpx_data)self.status_label.config(text="坐标随机偏移完成")def change_ele(self):self.show_param_dialog("修改海拔", [("系数", "1"), ("增加值", "0")], lambda params: gf.change_ele(self.gpx_data, int(params[0]), int(params[1])))def change_ele_climb(self):self.show_param_dialog("渐进爬升", [("递增系数", "1"), ("初始海拔", "100")], lambda params: gf.change_ele_climb(self.gpx_data, int(params[0]), int(params[1])))def make_time(self):self.show_param_dialog("生成时间序列", [("时间间隔系数", "1"), ("起始时间", "2023-01-01T00:00:00Z")], lambda params: gf.make_time(self.gpx_data, int(params[0]), params[1]))def clean_data(self):self.show_param_dialog("清理数据", [("关键字", "extensions")], lambda params: gf.clean_data(self.gpx_data, params[0]))def change_time(self):self.show_param_dialog("调整时间轴", [("时间缩放系数", "1.0"), ("时间偏移量(秒)", "0")], lambda params: gf.change_time(self.gpx_data, float(params[0]), int(params[1])))def copy_gpx(self):self.show_param_dialog("复制轨迹段", [("复制次数", "3"), ("随机偏移(0/1)", "0")], lambda params: gf.copy_gpx(self.gpx_data, int(params[0]), int(params[1])))def make_data_random(self, data_type):mapping = {"心率": "heartrate", "踏频": "cadence", "功率": "power", "距离": "distance", "速度": "speed"}keyword = mapping[data_type]self.show_param_dialog(f"生成{data_type}数据", [("平均值", "120"), ("波动范围", "20")], lambda params: gf.make_data_random(self.gpx_data, int(params[0]), int(params[1]), keyword))def read_gpx(self):if self.gpx_data:gpx_dict = gf.read_gpx(self.gpx_data)messagebox.showinfo("GPX数据", f"成功读取{len(gpx_dict['time'])}个数据点")def calculate_velocity(self):if self.gpx_data:gpx_dict = gf.read_gpx(self.gpx_data)df = gf.calculate_velocity(gpx_dict)messagebox.showinfo("速度计算", f"速度计算完成,平均速度: {df['velocity'].mean():.1f} km/h")def generate_srt(self):if self.gpx_data:srt_list = gf.gpx_to_srt(self.gpx_data)srt_path = self.srt_entry.get()if srt_path:gf.save_lines(srt_list, srt_path)messagebox.showinfo("SRT生成", f"SRT文件已保存: {srt_path}")def save_files(self):if self.gpx_data and self.output_entry.get():gf.save_lines(self.gpx_data, self.output_entry.get())messagebox.showinfo("保存", f"GPX文件已保存: {self.output_entry.get()}")def show_param_dialog(self, title, fields, callback):if not self.gpx_data:messagebox.showwarning("警告", "请先选择并加载GPX文件")returndialog = tk.Toplevel(self.root)dialog.title(title)dialog.geometry("300x200")self.center_window(dialog)  # 添加子窗口居中dialog.transient(self.root)dialog.grab_set()entries = []for i, (label, default) in enumerate(fields):tk.Label(dialog, text=label).grid(row=i, column=0, padx=10, pady=5, sticky="e")entry = tk.Entry(dialog)entry.insert(0, default)entry.grid(row=i, column=1, padx=10, pady=5)entries.append(entry)def apply_changes():try:params = [entry.get() for entry in entries]self.gpx_data = callback(params)dialog.destroy()self.status_label.config(text=f"{title}操作完成")except Exception as e:messagebox.showerror("错误", f"操作失败: {e}")tk.Button(dialog, text="确定", command=apply_changes).grid(row=len(fields), column=1, columnspan=2, pady=10)tk.Button(dialog, text="取消", command=dialog.destroy).grid(row=len(fields), column=2, columnspan=2, pady=10)if __name__ == "__main__":root = tk.Tk()app = GPXEditor(root)root.mainloop()

附录:列表的赋值类型和py打包

列表赋值

BUG复现

闲来无事写了个小程序 代码如下:

# -*- coding: utf-8 -*-
"""
Created on Fri Nov 19 19:47:01 2021@author: 16016
"""a_list = ['0','1','2','3','4','5','6','7','8','9','10','11','12','13','14','15']
#print(len(a_list))
#b_list = ['','','','','','','','','','','','','','','','']
c_list = [[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]]
#for i in range(16):
if len(a_list):for j in range(16):a_list[j]=str(a_list[j])+'_'+str(j)print("序号:",j)print('a_list:\n',a_list)c_list[j]=a_listprint('c_list[0]:\n',c_list[0])print('\n')
#        b_list[j]=a_list[7],a_list[8]
#        print(b_list[j])# 写入到Excel:
#print(c_list,'\n')    

我在程序中 做了一个16次的for循环 把列表a的每个值后面依次加上"_"和循环序号
比如循环第x次 就是把第x位加上_x 这一位变成x_x 我在输出测试中 列表a的每一次输出也是对的
循环16次后列表a应该变成[‘0_0’, ‘1_1’, ‘2_2’, ‘3_3’, ‘4_4’, ‘5_5’, ‘6_6’, ‘7_7’, ‘8_8’, ‘9_9’, ‘10_10’, ‘11_11’, ‘12_12’, ‘13_13’, ‘14_14’, ‘15_15’] 这也是对的

同时 我将每一次循环时列表a的值 写入到空列表c中 比如第x次循环 就是把更改以后的列表a的值 写入到列表c的第x位
第0次循环后 c[0]的值应该是[‘0_0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘10’, ‘11’, ‘12’, ‘13’, ‘14’, ‘15’] 这也是对的
但是在第1次循环以后 c[0]的值就一直在变 变成了c[x]的值
相当于把c_list[0]变成了c_list[1]…以此类推 最后得出的列表c的值也是每一项完全一样
我不明白这是怎么回事
我的c[0]只在第0次循环时被赋值了 但是后面它的值跟着在改变

如图:
在这里插入图片描述
第一次老出bug 赋值以后 每次循环都改变c[0]的值 搞了半天都没搞出来
无论是用appen函数添加 还是用二维数组定义 或者增加第三个空数组来过渡 都无法解决

代码改进

后来在我华科同学的指导下 突然想到赋值可以赋的是个地址 地址里面的值一直变化 导致赋值也一直变化 于是用第二张图的循环套循环深度复制实现了

代码如下:

# -*- coding: utf-8 -*-
"""
Created on Fri Nov 19 19:47:01 2021@author: 16016
"""a_list = ['0','1','2','3','4','5','6','7','8','9','10','11','12','13','14','15']
#print(len(a_list))
#b_list = ['','','','','','','','','','','','','','','','']
c_list = [[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]]
#for i in range(16):
if len(a_list):for j in range(16):a_list[j]=str(a_list[j])+'_'+str(j)print("序号:",j)print('a_list:\n',a_list)for i in range(16):c_list[j].append(a_list[i])print('c_list[0]:\n',c_list[0])print('\n')
#        b_list[j]=a_list[7],a_list[8]
#        print(b_list[j])# 写入到Excel:
print(c_list,'\n')    

解决了问题

在这里插入图片描述

优化

第三次是请教了老师 用copy函数来赋真值

代码如下:

# -*- coding: utf-8 -*-
"""
Created on Fri Nov 19 19:47:01 2021@author: 16016
"""a_list = ['0','1','2','3','4','5','6','7','8','9','10','11','12','13','14','15']
#print(len(a_list))
#b_list = ['','','','','','','','','','','','','','','','']
c_list = [[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]]
#for i in range(16):
if len(a_list):for j in range(16):a_list[j]=str(a_list[j])+'_'+str(j)print("序号:",j)print('a_list:\n',a_list)c_list[j]=a_list.copy()print('c_list[0]:\n',c_list[0])print('\n')
#        b_list[j]=a_list[7],a_list[8]
#        print(b_list[j])# 写入到Excel:
#print(c_list,'\n')    

同样能解决问题
在这里插入图片描述
最后得出问题 就是指针惹的祸!

a_list指向的是个地址 而不是值 a_list[i]指向的才是单个的值 copy()函数也是复制值而不是地址

如果这个用C语言来写 就直观一些了 难怪C语言是基础 光学Python不学C 遇到这样的问题就解决不了

C语言yyds Python是什么垃圾弱智语言

总结

由于Python无法单独定义一个值为指针或者独立的值 所以只能用列表来传送
只要赋值是指向一个列表整体的 那么就是指向的一个指针内存地址 解决方法只有一个 那就是将每个值深度复制赋值(子列表内的元素提取出来重新依次连接) 或者用copy函数单独赋值

如图测试:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
部分代码:

# -*- coding: utf-8 -*-
"""
Created on Sat Nov 20 16:45:48 2021@author: 16016
"""def text1():A=[1,2,3]B=[[],[],[]]for i in range(len(A)):A[i]=A[i]+iB[i]=Aprint(B)def text2():A=[1,2,3]B=[[],[],[]]A[0]=A[0]+0B[0]=Aprint(B)A[1]=A[1]+1B[1]=Aprint(B)A[2]=A[2]+2B[2]=Aprint(B)if __name__ == '__main__':text1()print('\n')text2()

py打包

Pyinstaller打包exe(包括打包资源文件 绝不出错版)

依赖包及其对应的版本号

PyQt5 5.10.1
PyQt5-Qt5 5.15.2
PyQt5-sip 12.9.0

pyinstaller 4.5.1
pyinstaller-hooks-contrib 2021.3

Pyinstaller -F setup.py 打包exe

Pyinstaller -F -w setup.py 不带控制台的打包

Pyinstaller -F -i xx.ico setup.py 打包指定exe图标打包

打包exe参数说明:

-F:打包后只生成单个exe格式文件;

-D:默认选项,创建一个目录,包含exe文件以及大量依赖文件;

-c:默认选项,使用控制台(就是类似cmd的黑框);

-w:不使用控制台;

-p:添加搜索路径,让其找到对应的库;

-i:改变生成程序的icon图标。

如果要打包资源文件
则需要对代码中的路径进行转换处理
另外要注意的是 如果要打包资源文件 则py程序里面的路径要从./xxx/yy换成xxx/yy 并且进行路径转换
但如果不打包资源文件的话 最好路径还是用作./xxx/yy 并且不进行路径转换

def get_resource_path(relative_path):if hasattr(sys, '_MEIPASS'):return os.path.join(sys._MEIPASS, relative_path)return os.path.join(os.path.abspath("."), relative_path)

而后再spec文件中的datas部分加入目录
如:

a = Analysis(['cxk.py'],pathex=['D:\\Python Test\\cxk'],binaries=[],datas=[('root','root')],hiddenimports=[],hookspath=[],hooksconfig={},runtime_hooks=[],excludes=[],win_no_prefer_redirects=False,win_private_assemblies=False,cipher=block_cipher,noarchive=False)

而后直接Pyinstaller -F setup.spec即可

如果打包的文件过大则更改spec文件中的excludes 把不需要的库写进去(但是已经在环境中安装了的)就行

这些不要了的库在上一次编译时的shell里面输出
比如:
在这里插入图片描述

在这里插入图片描述
然后用pyinstaller --clean -F 某某.spec


文章转载自:

http://PG4aLl9p.pLfrk.cn
http://Fs58EfVa.pLfrk.cn
http://06QMsKfm.pLfrk.cn
http://qyz76I2u.pLfrk.cn
http://LBO7Opg5.pLfrk.cn
http://tNKVw6cs.pLfrk.cn
http://mMVPTMMA.pLfrk.cn
http://Vyit8KNO.pLfrk.cn
http://mJUGd3Og.pLfrk.cn
http://Ff2Ap52b.pLfrk.cn
http://ytB9mUdb.pLfrk.cn
http://6wlsaFUq.pLfrk.cn
http://WEEdwkfu.pLfrk.cn
http://F61g4Eis.pLfrk.cn
http://wObCQh7V.pLfrk.cn
http://ibRPjskS.pLfrk.cn
http://LS17mRZh.pLfrk.cn
http://0eLxz9iE.pLfrk.cn
http://UvxWygkQ.pLfrk.cn
http://TVmXktFM.pLfrk.cn
http://Bttluq3T.pLfrk.cn
http://MjkH2G5k.pLfrk.cn
http://qxDtxUMn.pLfrk.cn
http://LMRM57l4.pLfrk.cn
http://aOZ5a2T2.pLfrk.cn
http://HX0AzSlt.pLfrk.cn
http://MqNuljyS.pLfrk.cn
http://gOCqBF7I.pLfrk.cn
http://XFX7C1Fh.pLfrk.cn
http://1WILA3jH.pLfrk.cn
http://www.dtcms.com/a/374336.html

相关文章:

  • 西嘎嘎学习 - C++vector容器 - Day 7
  • 第三章:Python基本语法规则详解(二)
  • Next系统总结学习(一)
  • 备考系统分析师-专栏介绍和目录
  • 【rk3229/rk3228a android7.1 LPDDR EMMC EMCP 批量sdk】
  • Kali 自带工具 dirb:Web 路径扫描与 edusrc 挖掘利器
  • 【系统分析师】第2章-基础知识:数学与工程基础(核心总结)
  • 房屋安全鉴定机构评价
  • JAVA:io字符流FileReader和FileWriter基础
  • 从零深入理解嵌入式OTA升级:Bootloader、IAP与升级流程全解析
  • 7.0 热电偶的工作原理
  • GPT(Generative Pre-trained Transformer)模型架构与损失函数介绍
  • 【51单片机】【protues仿真】基于51单片机公交报站系统
  • linux常用命令(2)——系统管理
  • Yarn介绍与HA搭建
  • 记个笔记:Cocos打包安卓使用安卓通信模块
  • 基于Python的云原生TodoList Demo 项目,验证云原生核心特性
  • 2025年- H121-Lc28. 找出字符串中第一个匹配项的下标(数组)--Java版
  • 【底层机制】auto 关键字的底层实现机制
  • 【代码随想录算法训练营——Day6(Day5周日休息)】哈希表——242.有效的字母异位词、349.两个数组的交集、202.快乐数、1.两数之和
  • leedcode 算法刷题第二八天
  • KafKa教程
  • 如何在 Ubuntu 22.04 中安装 Docker 引擎和 Linux 版 Docker Desktop 桌面软件
  • 基于RK3568/RK3588+全隔离串口+多电力协议接入电力网关机,用于新能源光伏风能等电站监测运维
  • 软件测试用例(沉淀中)
  • 华清远见25072班网络编程学习day1
  • 【Python办公】[ 网络剪切板 ]免费图床工具GUI界面打包(电脑上传-手机扫码下载)
  • [吾爱原创] 【游戏】王富贵的果菜园
  • Linux系统:线程同步与生产消费模型
  • 深入理解 IP 协议