将Codoon的路径记录导出成GPX路径
新浪微博网友 @better_man__ 问道能不能将 Codoon 记录的轨迹文件导出为 GPX 或 TCX 文件,以方便与Garmin、Nike+ Fuelband进行轨迹交换。试了一下,能够完成。过程如下。
GPX 和 TCX 文件
以下内容转自维基百科。
GPX(GPS eXchange Format, GPS交换格式)是一个XML格式,为应用软件设计的通用GPS数据格式。它可以用来描述路点、轨迹、路程。这个格式是免费的,可以在不需要付任何许可费用的前提下使用。它的标签保存位置,海拔和时间,可以用来在不同的GPS设备和软件之间交换数据。TCX, Garmin Training Center XML,功能类此。GARMIN 的 GPS 手表能够使用这两种格式的文件。
这两种格式之间已经有很多互相转换的工具。
实现说明
在 GPX 中,一个没有顺序关系的点集合,叫路点。一个有顺序的点的集合叫轨迹或者路程。轨迹是一个人曾经走过的记录,路程是一个建议的下一步要走的地方。所以,一般来讲,轨迹里的点,包含时间信息,路程里的点,没有时间信息。
下面的代码实现的是将 Codoon 的轨迹数据,转换成 GPX 路径文件。
获取Codoon轨迹数据
利用以前实现的咕咚 Codoon 运动的 API,能够获得某次用户运动的GPS轨迹。对应API详细说明参见 获取咕咚运动移动应用中的数据——非官方API
参考代码:
account = { "email" : "your@email" , "passwd" : "yourpassword" }
imei = "000000000000000"
device = DeviceCodoon ()
# login
device.get_users_login(account["email"], account["passwd"])
# 获得用户的运动历史列表
routes = device.get_route_log( productId = imei )
for r in routes["data"]:
routeId = r["route_id"]
print routeId
# 获得特定某次的运动轨迹
routeId = "03e1cd1e-07b1-11e3-b50f-00163e020001"
route = device.get_single_log( routeId = routeId )
device.saveJsonData( filename = "/single_log_20130817.json" , data = route)
取得的运动轨迹数据可以参考示例文件: single_log_20130817.json。这个轨迹是跟着绿野去长峪城腐败的轨迹。:D,摆两张照片放放毒。
GPX文件输出
Python 中有直接写 GPX 格式文件的包: gpxpy 。 用来创建 GPX 文件非常容易。函数中的参数 route 是 get_single_log 的结果。不过在使用中,如果将时间数据写入 GPX 文件时,gpxpy有个小Bug,需要对时间格式处理的部分做个小调整。
import gpxpy
import gpxpy.gpx
DATE_FORMAT = '%Y-%m-%dT%H:%M:%S'
def trans(route ):
points = route["data"]["points"]
# 创建 GPX 文件
gpx = gpxpy.gpx.GPX()
# Create first track in our GPX:
gpx_route = gpxpy.gpx.GPXRoute()
i = 1
for p in points:
tmpname = "#%5d" % i
tmptime = strptime( p["time_stamp"] , DATE_FORMAT )
lat = float(p["latitude"])
lon = float(p["longitude"])
# Create points
gpx_point = gpxpy.gpx.GPXRoutePoint( name = tmpname , longitude = lon , latitude = lat ,
elevation = p["elevation"] , time = tmptime )
# print gpx_point
gpx_route.points.append( gpx_point )
i = i + 1
gpx.routes.append(gpx_route)
# print 'Created GPX:', gpx.to_xml()
return gpx.to_xml()
gpxpy Bug 修正, gpx.py 文件
class GPXRoutePoint(mod_geo.Location):
...
def to_xml(self, version=None):
content = ''
if self.elevation is not None:
content += mod_utils.to_xml('ele', content=self.elevation)
if self.time:
# 需要修改的时间格式化部分
# content += mod_utils.to_xml('time', content=self.time.strftime(DATE_FORMAT))
content += mod_utils.to_xml('time', content=strftime(DATE_FORMAT , self.time))
...
class GPXTrackPoint(mod_geo.Location):
...
def to_xml(self, version=None):
content = ''
if self.elevation is not None:
content += mod_utils.to_xml('ele', content=self.elevation)
if self.time:
# 需要修改的时间格式化部分
# content += mod_utils.to_xml('time', content=self.time.strftime(DATE_FORMAT))
content += mod_utils.to_xml('time', content=strftime(DATE_FORMAT , self.time))
...
所生成的数据文件参见: single_log_20130817_shift.gpx。采用 GPXEditor 显示所生成的文件,发现直接输出的GPX文件在地图上有比较大的漂移,如下图所示,其中红线为真实路径的大致所在:
漂移修正
漂移修正首先需要有与经纬度相对应的offset文件,从 Codoon 的 APK 包中能够找到这个文件: city_offset_txt.txt
在代码中增加读取漂移信息、以及根据起始点计算所需增加的漂移修正的代码即可。代码修正为以下内容:
DATE_FORMAT = '%Y-%m-%dT%H:%M:%S'
CITY_OFFSET = "city_offset_txt.txt"
class CodoonRoute2Gpx:
offsetList = []
# 加载漂移数据文件
def loadCityOffset(self ):
fn = CITY_OFFSET
for line in fileinput.FileInput(fn):
cityInfo = line[:-1].split(";")
city = cityInfo[0].split(",")
offset = cityInfo[1].split(",")
self.offsetList.append( (city , offset) )
# print self.offsetList
# 计算与此点最近的偏移数据
def justifyCityOffset(self , lat ,lon):
if len( self.offsetList ) == 0 :
self.loadCityOffset()
nearest = 180 ** 2 + 180 ** 2
realoffset = None
for c in self.offsetList:
distance = (float(c[0][0]) - lat) ** 2 + (float(c[0][1]) - lon) ** 2
if distance < nearest :
nearest = distance
realoffset = c
return realoffset[1]
def trans(self , route ):
points = route["data"]["points"]
# 利用GPS起始点,计算在地图上的漂移
lat = points[0]["latitude"]
lon = points[0]["longitude"]
realoffset = self.justifyCityOffset( float(lat) ,float(lon) )
print realoffset
# 创建 GPX 文件
gpx = gpxpy.gpx.GPX()
# Create first track in our GPX:
gpx_route = gpxpy.gpx.GPXRoute()
i = 1
for p in points:
tmpname = "#%5d" % i
tmptime = strptime( p["time_stamp"] , DATE_FORMAT )
# 漂移修正
lat = float(p["latitude"]) + float(realoffset[0])
lon = float(p["longitude"]) + float(realoffset[1])
# Create points
gpx_point = gpxpy.gpx.GPXRoutePoint( name = tmpname , longitude = lon , latitude = lat ,
elevation = p["elevation"] , time = tmptime )
# print gpx_point
gpx_route.points.append( gpx_point )
i = i + 1
gpx.routes.append(gpx_route)
# print 'Created GPX:', gpx.to_xml()
return gpx.to_xml()
所生成的数据文件参见: single_log_20130817.gpx。采用 GPXEditor 显示所生成的文件,漂移已基本修正了:
Track 和 Route
新浪微博网友 @齐亮-Cavendish 发现如上文生成的 GPX 文件,没法导入 heiaheia.com , 经过检查,发现 heiaheia 只能够导入 Track 格式的 GPX 文件。
因此修改 GPX 输出代码如下,增加参数对导出格式的限定。
# Type 可以取值为 "track" or "route", 缺省为 "track"
def trans(self , route , type = "track"):
name = route["data"]["start_time"]
points = route["data"]["points"]
# Calculate offset of start point
lat = points[0]["latitude"]
lon = points[0]["longitude"]
realoffset = self.justifyCityOffset( float(lat) ,float(lon) )
print realoffset
gpx = gpxpy.gpx.GPX()
if type == "route":
# Create route in GPX Route Format:
rtname = "Route %s" % name
gpx_route = gpxpy.gpx.GPXRoute(name = rtname)
i = 1
for p in points:
tmpname = "#%5d" % i
tmptime = strptime( p["time_stamp"] , DATE_FORMAT )
lat = float(p["latitude"]) + float(realoffset[0])
lon = float(p["longitude"]) + float(realoffset[1])
gpx_point = gpxpy.gpx.GPXRoutePoint( name = tmpname , longitude = lon , latitude = lat ,
elevation = p["elevation"] , time = tmptime )
# print gpx_point
gpx_route.points.append( gpx_point )
i = i + 1
gpx.routes.append(gpx_route)
else:
# Create route in GPX Track Format:
trkname = "Track %s" % name
gpx_track = gpxpy.gpx.GPXTrack(name = trkname)
gpx_track_seg = gpxpy.gpx.GPXTrackSegment()
i = 1
for p in points:
tmpname = "#%5d" % i
tmptime = strptime( p["time_stamp"] , DATE_FORMAT )
lat = float(p["latitude"]) + float(realoffset[0])
lon = float(p["longitude"]) + float(realoffset[1])
gpx_point = gpxpy.gpx.GPXTrackPoint( name = tmpname , longitude = lon , latitude = lat ,
elevation = p["elevation"] , time = tmptime )
# print gpx_point
gpx_track_seg.points.append( gpx_point )
i = i + 1
gpx_track.segments.append(gpx_track_seg)
gpx.tracks.append(gpx_track)
# print 'Created GPX:', gpx.to_xml()
return gpx.to_xml()
使用 Track 格式的 GPX 文件,能够将数据导入 Heiaheia 网站,能够正确显示轨迹、里程、以及时长。
但是,还有一个问题,文件中Track记录为8月份的数据,并没有被自动放到8月份去,而是放在了当前 Walk 创建时所设定的日期:10月8日。
调用
account = { "email" : "your@email" , "passwd" : "yourpassword" }
imei = "000000000000000"
device = DeviceCodoon ()
# login
device.get_users_login(account["email"], account["passwd"])
# Trans Codoon GPS Data to GPX format
routeId = "03e1cd1e-07b1-11e3-b50f-00163e020001"
trans = CodoonRoute2Gpx()
trans.loadCityOffset()
route = device.get_single_log( routeId = routeId )
device.saveJsonData( filename = "/single_log_20130817.json" , data = route)
gtx = trans.trans( route = route , type = "route" )
device.saveXmlData( filename = "/single_log_20130817_route.gpx" , data = gtx)
gtx = trans.trans( route = route )
device.saveXmlData( filename = "/single_log_20130817_track.gpx" , data = gtx)
代码地址
https://github.com/iascchen/VisHealth/
源代码在 device/codoon.py 中。
玩的开心!
Author : iascchen(at)gmail(dot)com
Date : 2013-10-08
新浪微博 : @问天鼓原文链接:http://www.wearable.pw/index.php/archives/332