Merge branch 'master' of nano:src/igc2kmz

debian-sid
Tom Payne 15 years ago
commit 38836d21a3

@ -1,5 +1,6 @@
OLC2002=contrib/leonardo/olc2002
IGC2KMZ=bin/igc2kmz.py
IGC2TASK=bin/igc2task.py
BRAND2KML=bin/brand2kml.py
CC=gcc
@ -27,7 +28,11 @@ clean:
%: %.o
$(CC) -o $@ $(CFLAGS) $^ $(LIBS)
EXAMPLES=examples/2008-07-28-XPG-KVE-02.kmz examples/2008-06-16-xgd-001-01.kmz examples/858umbh1.kmz examples/2007-04-22-FLY-5094-01.kmz
EXAMPLES=examples/2008-07-28-XPG-KVE-02.kmz \
examples/2008-06-16-xgd-001-01.kmz \
examples/2008-09-05-CGP-XAGC-01-ebessos.kmz \
examples/858umbh1.kmz \
examples/2007-04-22-FLY-5094-01.kmz
.PRECIOUS: $(EXAMPLES:%.kmz=%.olc)
examples: $(EXAMPLES)
@ -189,6 +194,18 @@ examples/2008-07-28-XPG-KVE-02.kmz: examples/2008-07-28-XPG-KVE-02.igc examples/
-u http://www.xcontest.org/2008/world/en/flights/detail:charlie/28.7.2008/09:23 \
-x examples/2008-07-28-XPG-KVE-02.gpx
examples/2008-09-05-CGP-XAGC-01-ebessos.kmz: examples/2008-09-05-CGP-XAGC-01-ebessos.igc examples/2008-09-05-CGP-XAGC-01-ebessos.gpx
$(IGC2KMZ) -z 3 -o $@ \
-i $< \
-t examples/2008-09-05-CGP-XAGC-01-ebessos.gpx
examples/2008-09-05-CGP-XAGC-01-ebessos.gpx: examples/2008-09-05-CGP-XAGC-01-ebessos.igc
$(IGC2TASK) examples/2008-09-05-CGP-XAGC-01-ebessos.igc \
-o $@ \
--tz-offset 3 \
--start-time 14:30 \
--start-radius 1000
examples/858umbh1.kmz: examples/858umbh1.igc examples/858umbh1.gpx examples/xcontest.kml
$(IGC2KMZ) -z 2 -o $@ -r examples/xcontest.kml \
-i $< \

@ -1,6 +1,9 @@
HIGH
Check indexes in thermal folder
Check indexes in colored tracklogs
Check why balloons don't point at point
Clean up heirarchical altitude marks
Clean up global styles
MEDIUM
Allow override of photo time

@ -23,17 +23,18 @@ import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
import igc2kmz
import igc2kmz.igc
import igc2kmz.kml
import igc2kmz.photo
import igc2kmz.xc
from igc2kmz import Flight, flights2kmz
from igc2kmz.igc import IGC
from igc2kmz.kml import Verbatim
from igc2kmz.photo import Photo
from igc2kmz.task import Task
from igc2kmz.xc import XC
def add_flight(option, opt, value, parser):
"""Add a flight."""
igc = igc2kmz.igc.IGC(open(value))
parser.values.flights.append(igc2kmz.Flight(igc.track()))
igc = IGC(open(value))
parser.values.flights.append(Flight(igc.track()))
def set_flight_option(option, opt, value, parser):
@ -45,7 +46,7 @@ def set_flight_option(option, opt, value, parser):
def add_photo(option, opt, value, parser):
"""Add a photo to the last flight."""
flight = parser.values.flights[-1]
photo = igc2kmz.photo.Photo(value)
photo = Photo(value)
flight.photos.append(photo)
@ -59,7 +60,7 @@ def set_photo_option(option, opt, value, parser):
def set_flight_xc(option, opt, value, parser):
"""Set the XC of the last flight."""
flight = parser.values.flights[-1]
xc = igc2kmz.xc.XC(open(value))
xc = XC.from_file(open(value))
flight.xc = xc
@ -74,6 +75,8 @@ def main(argv):
parser.add_option('-r', '--root', metavar='FILENAME',
action='append', dest='roots',
help='add root element')
parser.add_option('-t', '--task', metavar='FILENAME',
help='set task')
parser.add_option('--debug',
action='store_true',
help='enable pretty KML output')
@ -121,10 +124,12 @@ def main(argv):
if len(args) != 1:
parser.error('extra arguments on command line: %s' % repr(args[1:]))
#
roots = [igc2kmz.kml.Verbatim(open(root).read()) for root in options.roots]
kmz = igc2kmz.flights2kmz(options.flights,
roots=roots,
tz_offset=options.tz_offset)
roots = [Verbatim(open(root).read()) for root in options.roots]
task = Task.from_file(open(options.task)) if options.task else None
kmz = flights2kmz(options.flights,
roots=roots,
tz_offset=options.tz_offset,
task=task)
kmz.write(options.output, debug=options.debug)

@ -0,0 +1,159 @@
#!/usr/bin/python
#
# bin/igc2task.py IGC to task converter
# Copyright (C) 2008 Tom Payne
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import with_statement
from datetime import timedelta
from optparse import OptionParser
import os
import re
import sys
try:
from xml.etree.cElementTree import ElementTree, TreeBuilder
except ImportError:
from xml.etree.ElementTree import ElementTree, TreeBuilder
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from igc2kmz.coord import Coord
from igc2kmz.etree import tag
from igc2kmz.igc import IGC
from igc2kmz.task import Task, Turnpoint
def find_nth(function, iterable, n=0):
for element in iterable:
if function(element):
if n <= 0:
return element
else:
n -= 1
return None
def main(argv):
parser = OptionParser(
usage='Usage: %prog [options] filename.igc',
description='IGC to task converter')
parser.add_option('-o', '--output', metavar='FILENAME',
help='set output filename')
parser.add_option('-n', '--name', metavar='NAME',
help='set task name')
parser.add_option('-z', '--tz-offset', metavar='HOURS', type='int',
help='set timezone offset')
parser.add_option('--start', metavar='NAME',
help='set start turnpoint')
parser.add_option('--start-count', metavar='NUMBER', type='int',
help='set start count')
parser.add_option('--start-radius', metavar='RADIUS', type='int',
help='set start radius in meters')
parser.add_option('--start-time', metavar='TIME',
help='set start time')
parser.add_option('--ess', metavar='NAME',
help='set end of speed section turnpoint')
parser.add_option('--ess-count', metavar='NUMBER', type='int',
help='set end of speed section count')
parser.add_option('--ess-radius', metavar='RADIUS', type='int',
help='set end of speed section radius in meters')
parser.add_option('--goal', metavar='NAME',
help='set goal turnpoint')
parser.add_option('--goal-count', metavar='NUMBER', type='int',
help='set goal count')
parser.add_option('--goal-radius', metavar='RADIUS', type='int',
help='set start radius in meters')
parser.set_defaults(tz_offset=0)
parser.set_defaults(start_count=0)
parser.set_defaults(ess_count=0)
parser.set_defaults(goal_count=0)
#
options, args = parser.parse_args(argv)
if len(args) < 2:
parser.error('no IGC file specified')
if len(args) > 2:
parser.error('excess arguments on command line: %s' % repr(args[2:]))
#
igc = IGC(open(argv[1]))
if not igc.c:
parser.error('%s does not contain a task' % repr(argv[1]))
tps = []
for c in igc.c:
if c.name == 'TAKEOFF' or c.name == 'LANDING':
continue
m = re.match(r'([A-Z][0-9]{2})([0-9]{3})', c.name)
if m:
name = m.group(1)
ele = 10 * int(m.group(2))
else:
name = c.name
ele = 0
coord = Coord(c.lat, c.lon, ele)
tp = Turnpoint(name, coord)
tps.append(tp)
task = Task(options.name, tps)
#
if options.start:
start = find_nth(lambda tp: tp.name == options.start, task.tps,
options.start_count)
if not start:
parser.error('start turnpoint %s not found' % repr(options.start))
else:
start = task.tps[0]
if options.start_radius:
start.radius = int(options.start_radius)
if options.start_time:
m = re.match(r'(\d+):(\d\d)\Z', options.start_time)
if not m:
parser.error('invalid start time %s' % repr(options.start_time))
hour, minute = map(int, m.group(1, 2))
start.coord.dt = igc.b[0].dt.replace(hour=hour,
minute=minute,
second=0) \
- timedelta(seconds=3600 * options.tz_offset)
#
if options.ess:
ess = find_nth(lambda tp: tp.name == options.ess, task.tps,
options.ess_count)
if not ess:
parser.error('end of speed section turnpoint %s not found'
% repr(options.ess))
else:
options.ess = task.tps[-2]
if options.ess_radius:
ess.radius = int(options.ess_radius)
#
if options.goal:
goal = find_nth(lambda tp: tp.name == options.goal, task.tps,
options.goal_count)
if not goal:
parser.error('goal turnpoint %s not found' % repr(options.goal))
else:
goal = task.tps[-1]
if options.goal_radius:
goal.radius = int(options.goal_radius)
#
attrs = {'version': '1.1',
'creator': 'http://github.com/twpayne/igc2kmz/wikis'}
with tag(TreeBuilder(), 'gpx', attrs) as tb:
element = task.build_tree(tb).close()
output = open(options.output, 'w') if options.output else sys.stdout
output.write('<?xml version="1.0" encoding="utf-8"?>')
ElementTree(element).write(output)
if __name__ == '__main__':
main(sys.argv)

@ -22,7 +22,7 @@ from __future__ import with_statement
import datetime
import fileinput
import logging
import optparse
from optparse import OptionParser
import os
import re
import sys
@ -33,51 +33,9 @@ except ImportError:
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from igc2kmz.coord import Coord
from igc2kmz.etree import tag
class RtePt(object):
def __init__(self):
self.name = None
self.lat = None
self.lon = None
self.dt = None
def togpx(self, tb):
with tag(tb, 'rtept', {'lat': str(self.lat), 'lon': str(self.lon)}):
with tag(tb, 'name'): tb.data(self.name)
with tag(tb, 'time'):
tb.data(self.dt.strftime('%Y-%m-%dT%H:%M:%SZ'))
with tag(tb, 'fix'): tb.data('2d')
return tb
class Rte(object):
def __init__(self):
self.league = None
self.name = None
self.distance = None
self.multiplier = None
self.score = None
self.circuit = None
self.rtepts = []
def togpx(self, tb):
with tag(tb, 'rte'):
with tag(tb, 'name'): tb.data(self.name)
with tag(tb, 'extensions'):
with tag(tb, 'league'): tb.data(self.league)
with tag(tb, 'distance'): tb.data(str(self.distance))
with tag(tb, 'multiplier'): tb.data('%.2f' % self.multiplier);
with tag(tb, 'score'): tb.data(str(self.score))
if self.circuit:
with tag(tb, 'circuit'):
pass
for rtept in self.rtepts:
rtept.togpx(tb)
return tb
from igc2kmz.xc import Route, Turnpoint, XC
DEBUG_DATE_RE = re.compile(r'DEBUG DATE (\d\d)(\d\d)(\d\d)\Z')
@ -98,80 +56,71 @@ PRETTY_NAME = {
CIRCUITS = set(['FREE_TRIANGLE', 'FAI_TRIANGLE'])
class XC(object):
def __init__(self, file, league, debug=False):
self.rtes = []
date = None
for line in file:
line = line.rstrip()
m = DEBUG_DATE_RE.match(line)
if m:
day, mon, year = map(int, m.groups())
date = datetime.date(year + 2000, mon,day)
continue
m = OUT_TYPE_RE.match(line)
if m:
rte = Rte()
rte.league = league
rte.name = PRETTY_NAME[m.group(1)]
rte.circuit = m.group(1) in CIRCUITS
self.rtes.append(rte)
last_time = None
continue
m = OUT_FLIGHT_KM_RE.match(line)
if m:
rte.distance = float(m.group(1))
continue
m = OUT_FLIGHT_POINTS_RE.match(line)
if m:
rte.score = float(m.group(1))
rte.multiplier = rte.score / rte.distance
continue
m = OUT_P_RE.match(line)
if m:
rtept = RtePt()
rtept.name = 'TP%d' % len(rte.rtepts) if rte.rtepts else 'Start'
rtept.lat = int(m.group(5)) + float(m.group(6)) / 60.0
if m.group(4) == 'S':
rtept.lat = -rtept.lat
rtept.lon = int(m.group(8)) + float(m.group(9)) / 60.0
if m.group(7) == 'W':
rtept.lon = -rtept.lon
time = datetime.time(*map(int, m.group(1, 2, 3)))
if not last_time is None and time < last_time:
date += datetime.timedelta(1)
rtept.dt = datetime.datetime.combine(date, time)
rte.rtepts.append(rtept)
last_time = time
continue
if debug:
logging.warning(line)
for rte in self.rtes:
rte.rtepts[-1].name = 'Finish'
def togpx(self, tb):
attrs = {'version': '1.1',
'creator': 'http://github.com/twpayne/igc2kmz/wikis'}
with tag(tb, 'gpx', attrs):
for rte in self.rtes:
rte.togpx(tb)
return tb
def main(argv):
parser = optparse.OptionParser(description='olc2002 to GPX converter')
parser = OptionParser(description='olc2002 to GPX converter')
parser.add_option('-l', '--league', metavar='STRING')
parser.add_option('-o', '--output', metavar='FILENAME')
parser.add_option('--debug', action='store_true')
parser.set_defaults(debug=False)
parser.set_defaults(league='OLC')
options, args = parser.parse_args(argv)
xc = XC(fileinput.input(args[1:]), options.league, debug=options.debug)
e = xc.togpx(TreeBuilder()).close()
#
routes = []
date = None
for line in fileinput.input(args[1:]):
line = line.rstrip()
m = DEBUG_DATE_RE.match(line)
if m:
day, mon, year = map(int, m.groups())
date = datetime.date(year + 2000, mon,day)
continue
m = OUT_TYPE_RE.match(line)
if m:
name = PRETTY_NAME[m.group(1)]
circuit = m.group(1) in CIRCUITS
route = Route(name, options.league, None, None, None, circuit, [])
routes.append(route)
last_time = None
continue
m = OUT_FLIGHT_KM_RE.match(line)
if m:
route.distance = float(m.group(1))
continue
m = OUT_FLIGHT_POINTS_RE.match(line)
if m:
route.score = float(m.group(1))
route.multiplier = route.score / route.distance
continue
m = OUT_P_RE.match(line)
if m:
name = 'TP%d' % len(route.tps) if route.tps else 'Start'
lat = int(m.group(5)) + float(m.group(6)) / 60.0
if m.group(4) == 'S':
lat = -lat
lon = int(m.group(8)) + float(m.group(9)) / 60.0
if m.group(7) == 'W':
lon = -lon
time = datetime.time(*map(int, m.group(1, 2, 3)))
if not last_time is None and time < last_time:
date += datetime.timedelta(1)
dt = datetime.datetime.combine(date, time)
coord = Coord(lat, lon, 0, dt)
tp = Turnpoint(name, coord)
route.tps.append(tp)
last_time = time
continue
if options.debug:
logging.warning(line)
for route in routes:
route.tps[-1].name = 'Finish'
xc = XC(routes)
attrs = {'version': '1.1',
'creator': 'http://github.com/twpayne/igc2kmz/wikis'}
with tag(TreeBuilder(), 'gpx', attrs) as tb:
element = xc.build_tree(tb).close()
output = open(options.output, 'w') if options.output else sys.stdout
output.write('<?xml version="1.0" encoding="UTF-8"?>')
ElementTree(e).write(output)
ElementTree(element).write(output)
if __name__ == '__main__':

File diff suppressed because it is too large Load Diff

@ -24,6 +24,7 @@ import urlparse
import third_party.pygooglechart as pygooglechart
import color
from coord import rad_to_compass
import kml
import kmz
import scale
@ -197,10 +198,10 @@ class Flight(object):
def make_snippet(self, globals):
if self.xc:
rte = sorted(self.xc.rtes,
key=operator.attrgetter('score'),
reverse=True)[0]
xc = '%.1fkm %s' % (rte.distance, rte.name)
route = sorted(self.xc.routes,
key=operator.attrgetter('score'),
reverse=True)[0]
xc = '%.1fkm %s' % (route.distance, route.name)
else:
xc = None
date = self.track.bounds.time.min + globals.tz_offset
@ -371,10 +372,11 @@ class Flight(object):
folder = kml.Folder(name='Altitude marks',
styleUrl=style_url,
visibility=0)
for index in util.salient([c.ele for c in self.track.coords], 100):
for index, j in util.salient2([c.ele for c in self.track.coords],
[100, 50, 10]):
coord = self.track.coords[index]
i = globals.scales.altitude.discretize(coord.ele)
style_url = globals.altitude_styles[i].url()
style_url = globals.altitude_styles[j][i].url()
folder.add(self.make_placemark(globals,
coord,
altitudeMode='absolute',
@ -412,19 +414,19 @@ class Flight(object):
return kmz.kmz(folder)
def make_xc_folder(self, globals):
def make_row(rte, i, j, percentage=False):
distance = rte.rtepts[i].coord.distance_to(rte.rtepts[j].coord)
def make_row(route, i, j, percentage=False):
distance = route.tps[i].coord.distance_to(route.tps[j].coord)
th = '%s %s %s' \
% (rte.rtepts[i].name, RIGHTWARDS_ARROW, rte.rtepts[j].name)
% (route.tps[i].name, RIGHTWARDS_ARROW, route.tps[j].name)
if percentage:
td = '%.1fkm (%.1f%%)' \
% (distance / 1000.0, 0.1 * distance / rte.distance)
% (distance / 1000.0, 0.1 * distance / route.distance)
else:
td = '%.1fkm' % (distance / 1000.0)
return (th, td)
def make_leg(rte, i, j, name=True, arrow=False, style_url=None):
coord0 = rte.rtepts[i].coord
coord1 = rte.rtepts[j].coord
def make_leg(route, i, j, name=True, arrow=False, style_url=None):
coord0 = route.tps[i].coord
coord1 = route.tps[j].coord
line_string = kml.LineString(coordinates=[coord0, coord1],
altitudeMode='clampToGround',
tessellate=1)
@ -451,64 +453,64 @@ class Flight(object):
style_url = globals.stock.radio_folder_style.url()
folder = kml.Folder(name='Cross country', open=0, styleUrl=style_url)
folder.add(globals.stock.invisible_none_folder)
for rank, rte in enumerate(sorted(self.xc.rtes,
for rank, route in enumerate(sorted(self.xc.routes,
key=operator.attrgetter('score'),
reverse=True)):
rows = []
rows.append(('League', rte.league))
rows.append(('Type', rte.name[0].upper() + rte.name[1:]))
if rte.circuit:
if len(rte.rtepts) == 4:
rows.append(make_row(rte, 1, 2))
rows.append(make_row(rte, 2, 1))
rows.append(('League', route.league))
rows.append(('Type', route.name[0].upper() + route.name[1:]))
if route.circuit:
if len(route.tps) == 4:
rows.append(make_row(route, 1, 2))
rows.append(make_row(route, 2, 1))
else:
for i in xrange(1, len(rte.rtepts) - 2):
rows.append(make_row(rte, i, i + 1, percentage=True))
rows.append(make_row(rte, -2, 1, percentage=True))
for i in xrange(1, len(route.tps) - 2):
rows.append(make_row(route, i, i + 1, percentage=True))
rows.append(make_row(route, -2, 1, percentage=True))
else:
for i in xrange(0, len(rte.rtepts) - 1):
rows.append(make_row(rte, i, i + 1))
rows.append(('Distance', '%.1fkm' % rte.distance))
for i in xrange(0, len(route.tps) - 1):
rows.append(make_row(route, i, i + 1))
rows.append(('Distance', '%.1fkm' % route.distance))
rows.append(('Multiplier',
'%s %.2f points/km' % (MULTIPLICATION_SIGN,
rte.multiplier)))
rows.append(('Score', '<b>%.2f points</b>' % rte.score))
if rte.circuit:
rows.append(make_row(rte, -1, 0))
route.multiplier)))
rows.append(('Score', '<b>%.2f points</b>' % route.score))
if route.circuit:
rows.append(make_row(route, -1, 0))
description = '<table>%s</table>' % ''.join('<tr><th align="right">%s</th><td>%s</td></tr>' % row for row in rows)
name = '%.1fkm %s (%.2f points)' \
% (rte.distance, rte.name, rte.score)
% (route.distance, route.name, route.score)
visibility = 1 if rank == 0 else 0
style_url = globals.stock.check_hide_children_style.url()
rte_folder = kml.Folder(name=name,
route_folder = kml.Folder(name=name,
description=kml.CDATA(description),
Snippet=None,
styleUrl=style_url,
visibility=visibility)
for rtept in rte.rtepts:
coord = self.track.coord_at(rtept.coord.dt)
for tp in route.tps:
coord = self.track.coord_at(tp.coord.dt)
point = kml.Point(coordinates=[coord],
altitudeMode=self.altitude_mode,
extrude=1)
style_url = globals.stock.xc_style.url()
placemark = kml.Placemark(point,
name=rtept.name,
name=tp.name,
styleUrl=style_url)
rte_folder.add(placemark)
if rte.circuit:
rte_folder.add(make_leg(rte, 0, 1, name=None, arrow=True))
if len(rte.rtepts) == 4:
rte_folder.add(make_leg(rte, 1, 2))
route_folder.add(placemark)
if route.circuit:
route_folder.add(make_leg(route, 0, 1, name=None, arrow=True))
if len(route.tps) == 4:
route_folder.add(make_leg(route, 1, 2))
else:
for i in xrange(1, len(rte.rtepts) - 2):
rte_folder.add(make_leg(rte, i, i + 1, arrow=True))
for i in xrange(1, len(route.tps) - 2):
route_folder.add(make_leg(route, i, i + 1, arrow=True))
style_url = globals.stock.xc_style2.url()
rte_folder.add(make_leg(rte, -2, 1, style_url=style_url))
rte_folder.add(make_leg(rte, -2, -1, name=None, arrow=True))
route_folder.add(make_leg(route, -2, 1, style_url=style_url))
route_folder.add(make_leg(route, -2, -1, name=None, arrow=True))
else:
for i in xrange(0, len(rte.rtepts) - 1):
rte_folder.add(make_leg(rte, i, i + 1, arrow=True))
folder.add(rte_folder)
for i in xrange(0, len(route.tps) - 1):
route_folder.add(make_leg(route, i, i + 1, arrow=True))
folder.add(route_folder)
return kmz.kmz(folder)
def make_analysis_folder(self, globals, title, slices, style_url):
@ -521,8 +523,8 @@ class Flight(object):
for sl in slices:
coord0 = self.track.coords[sl.start]
coord1 = self.track.coords[sl.stop]
midpoint = coord0.halfway_to(coord1)
point = kml.Point(coordinates=[midpoint], altitudeMode='absolute')
coord = coord0.halfway_to(coord1)
point = kml.Point(coordinates=[coord], altitudeMode='absolute')
line_string = kml.LineString(coordinates=[coord0, coord1],
altitudeMode='absolute')
multi_geometry = kml.MultiGeometry(point, line_string)
@ -578,7 +580,7 @@ class Flight(object):
'%dm' % total_dz_negative))
if title == 'thermal':
drift_speed = dp / dt
drift_direction = coord.rad_to_compass(theta + math.pi)
drift_direction = rad_to_compass(theta + math.pi)
rows.append(('Drift', '%.1fkm/h %s'
% (3.6 * drift_speed, drift_direction)))
trs = ''.join('<tr><th align="right">%s</th><td>%s</td></tr>' % row
@ -705,7 +707,49 @@ class Flight(object):
return folder
def flights2kmz(flights, roots=[], tz_offset=0):
def make_task_folder(globals, task):
# TODO add description
style_url = globals.stock.check_hide_children_style.url()
folder = kml.Folder(name='Task', styleUrl=style_url)
style_url = globals.stock.xc_style.url()
done = set()
for tp in task.tps:
key = tp.name
if key in done:
continue
else:
done.add(key)
point = kml.Point(coordinates=[tp.coord])
folder.add(kml.Placemark(point, name=tp.name, styleUrl=style_url))
done = set()
for tp in task.tps:
key = (tp.name, tp.radius)
if key in done:
continue
else:
done.add(key)
coordinates = kml.coordinates.circle(tp.coord, tp.radius)
line_string = kml.LineString(coordinates, tessellate=1)
folder.add(kml.Placemark(line_string, styleUrl=style_url))
for i in xrange(0, len(task.tps) - 1):
tp0 = task.tps[i]
tp1 = task.tps[i + 1]
coord0 = tp0.coord.coord_at(tp0.coord.initial_bearing_to(tp1.coord),
tp0.radius)
theta = tp1.coord.initial_bearing_to(tp0.coord)
coord1 = tp1.coord.coord_at(theta, tp1.radius)
line_string1 = kml.LineString(coordinates=[coord0, coord1],
tessellate=1)
coords = [coord1.coord_at(theta - math.pi / 12.0, 400.0),
coord1,
coord1.coord_at(theta + math.pi / 12.0, 400.0)]
line_string2 = kml.LineString(coordinates=coords, tessellate=1)
multi_geometry = kml.MultiGeometry(line_string1, line_string2)
folder.add(kml.Placemark(multi_geometry, styleUrl=style_url))
return kmz.kmz(folder)
def flights2kmz(flights, roots=[], tz_offset=0, task=None):
stock = Stock()
globals = util.OpenStruct()
globals.stock = stock
@ -715,21 +759,25 @@ def flights2kmz(flights, roots=[], tz_offset=0):
if globals.bounds.climb.min < -5.0:
globals.bounds.climb.min = -5.0
globals.tz_offset = datetime.timedelta(0, 3600 * tz_offset)
globals.task = task
globals.scales = util.OpenStruct()
globals.scales.altitude = scale.Scale(globals.bounds.ele.tuple(),
title='altitude',
gradient=color.default_gradient)
globals.altitude_styles = []
for c in globals.scales.altitude.colors():
balloon_style = kml.BalloonStyle(text='$[description]')
icon_style = kml.IconStyle(globals.stock.icons[0],
color=c,
scale=globals.stock.icon_scales[0])
label_style = kml.LabelStyle(color=c,
scale=globals.stock.label_scales[0])
style = kml.Style(balloon_style, icon_style, label_style)
globals.altitude_styles.append(style)
stock.kmz.add_roots(*globals.altitude_styles)
for i in xrange(0, 3):
altitude_styles = []
for c in globals.scales.altitude.colors():
balloon_style = kml.BalloonStyle(text='$[description]')
icon_style = kml.IconStyle(globals.stock.icons[i],
color=c,
scale=globals.stock.icon_scales[i])
label_style = kml.LabelStyle(color=c,
scale=globals.stock.label_scales[i])
style = kml.Style(balloon_style, icon_style, label_style)
altitude_styles.append(style)
stock.kmz.add_roots(*altitude_styles)
globals.altitude_styles.append(altitude_styles)
gradient = color.bilinear_gradient
globals.scales.climb = scale.ZeroCenteredScale(globals.bounds.climb.tuple(),
title='climb',
@ -749,6 +797,8 @@ def flights2kmz(flights, roots=[], tz_offset=0):
result = kmz.kmz()
result.add_siblings(stock.kmz)
result.add_roots(*roots)
if globals.task:
result.add_siblings(make_task_folder(globals, globals.task))
for flight in flights:
result.add_siblings(flight.to_kmz(globals))
return result

@ -24,6 +24,7 @@ class tag(object):
def __enter__(self):
self.tb.start(self.name, self.attrs)
return self.tb
def __exit__(self, type, value, traceback):
self.tb.end(self.name)

@ -20,7 +20,7 @@ import logging
import os.path
import re
import coord
from coord import Coord
import track
@ -202,7 +202,7 @@ class IGC(object):
def track(self):
ele = 'ele' if any(b.ele for b in self.b) else 'alt'
coords = [coord.Coord.deg(b.lat, b.lon, getattr(b, ele), b.dt)
coords = [Coord.deg(b.lat, b.lon, getattr(b, ele), b.dt)
for b in self.b]
kwargs = {}
kwargs['filename'] = os.path.basename(self.filename)

@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import math
from math import acos, ceil, pi
class_by_name = {}
@ -75,7 +75,8 @@ class _SimpleElement(_Element):
if self.text is None:
return '<%s%s/>' % (self.name(), attrs)
else:
return '<%s%s>%s</%s>' % (self.name(), attrs, self.text, self.name())
return '<%s%s>%s</%s>' \
% (self.name(), attrs, self.text, self.name())
class _CompoundElement(_Element):
@ -130,7 +131,10 @@ class _CompoundElement(_Element):
if len(self.children) == 0:
return '<%s%s/>' % (self.name(), attrs)
else:
return '<%s%s>%s</%s>' % (self.name(), attrs, ''.join(map(str, self.children)), self.name())
return '<%s%s>%s</%s>' % (self.name(),
attrs,
''.join(map(str, self.children)),
self.name())
class Verbatim(_Element):
@ -183,7 +187,20 @@ class color(_SimpleElement):
class coordinates(_SimpleElement):
def __init__(self, coords):
_SimpleElement.__init__(self, ' '.join('%f,%f,%d' % (180.0 * coord.lon / math.pi, 180.0 * coord.lat / math.pi, coord.ele) for coord in coords))
texts = ('%f,%f,%d' % (180.0 * c.lon / pi, 180.0 * c.lat / pi, c.ele)
for c in coords)
_SimpleElement.__init__(self, ' '.join(texts))
@classmethod
def circle(cls, center, radius, ele=None, error=0.1):
decimation = int(ceil(pi / acos((radius - error) / (radius + error))))
coords = []
for i in xrange(0, decimation + 1):
coord = center.coord_at(-2.0 * pi * i / decimation, radius + error)
if ele:
coord.ele = ele
coords.append(coord)
return cls(coords)
class description(_SimpleElement): pass
@ -199,9 +216,11 @@ class Icon(_CompoundElement):
@classmethod
def character(cls, c, extra=''):
if ord('1') <= ord(c) <= ord('9'):
return cls.palette(3, (ord(c) - ord('1')) % 8 + 16 * ((ord(c) - ord('1')) / 8), extra)
icon = (ord(c) - ord('1')) % 8 + 16 * ((ord(c) - ord('1')) / 8)
return cls.palette(3, icon, extra)
elif ord('A') <= ord(c) <= ord('Z'):
return cls.palette(5, (ord(c) - ord('A')) % 8 + 16 * ((31 - ord(c) + ord('A')) / 8), extra)
icon = (ord(c) - ord('A')) % 8 + 16 * ((31 - ord(c) + ord('A')) / 8)
return cls.palette(5, icon, extra)
else:
return cls.default()
@ -211,7 +230,9 @@ class Icon(_CompoundElement):
@classmethod
def palette(cls, pal, icon, extra=''):
return cls(href='http://maps.google.com/mapfiles/kml/pal%d/icon%d%s.png' % (pal, icon, extra))
href = 'http://maps.google.com/mapfiles/kml/pal%d/icon%d%s.png' \
% (pal, icon, extra)
return cls(href=href)
@classmethod
def none(cls):

@ -20,7 +20,7 @@ import os.path
import urllib2
import urlparse
import coord
from coord import Coord
import exif
@ -57,7 +57,7 @@ class Photo(object):
else:
ele = 0
self.elevation_data = False
self.coord = coord.Coord.deg(lat, lon, ele)
self.coord = Coord.deg(lat, lon, ele)
else:
self.coord = None
self.elevation_data = None

@ -0,0 +1,130 @@
# igc2kmz.py igc2kmz competition task module
# Copyright (C) 2008 Tom Payne
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import with_statement
import datetime
try:
from xml.etree.cElementTree import ElementTree, TreeBuilder, parse
except ImportError:
from xml.etree.ElementTree import ElementTree, TreeBuilder, parse
from coord import Coord
from etree import tag
class Turnpoint(object):
def __init__(self, name, coord, radius=400, enter=True, desc=None):
self.name = name
self.coord = coord
self.radius = radius
self.enter = enter
self.desc = desc
def trigger(self, coord0, coord1):
if self.enter:
if self.coord.distance_to(coord0) < self.radius:
return False
if self.coord.distance_to(coord1) > self.radius:
return False
if coord0.dt < self.dt:
return False
return True
else:
if self.coord.distance_to(coord0) > self.radius:
return False
if self.coord.distance_to(coord1) < self.radius:
return False
if coord0.dt < self.dt:
return False
return True
def build_tree(self, tb):
attrs = {'lat': str(self.coord.lat), 'lon': str(self.coord.lon)}
with tag(tb, 'rtept', attrs):
with tag(tb, 'name'):
tb.data(self.name)
if self.desc:
with tag(tb, 'desc'):
tb.data(self.desc)
if self.coord.ele:
with tag(tb, 'ele'):
tb.data(str(self.coord.ele))
if self.coord.dt:
with tag(tb, 'time'):
tb.data(self.coord.dt.strftime('%Y-%m-%dT%H:%M:%SZ'))
if self.radius != 400 or not self.enter:
with tag(tb, 'extensions'):
if self.radius != 400:
with tag(tb, 'radius'):
tb.data('%d' % self.radius)
if not self.enter:
with tag(tb, 'exit'):
pass
return tb
@classmethod
def from_element(cls, element):
name = element.findtext('name').encode('utf_8')
desc_tag = element.find('desc')
desc = desc_tag.text.encode('utf_8') if desc_tag else None
lat = float(element.get('lat'))
lon = float(element.get('lon'))
ele_tag = element.find('ele')
ele = int(ele_tag.text) if ele_tag else 0
time_tag = element.find('time')
if time_tag:
dt = datetime.datetime.strptime(time_tag.text, '%Y-%m-%dT%H:%M:%SZ')
else:
dt = None
coord = Coord.deg(lat, lon, ele, dt)
radius_tag = element.find('extensions/radius')
radius = int(radius_tag.text) if radius_tag else 400
enter = element.find('extensions/exit') is None
return cls(name, coord, radius, enter, desc)
class Task(object):
def __init__(self, name, tps):
self.name = name
self.tps = tps
def build_tree(self, tb):
with tag(tb, 'rte'):
if self.name:
with tag(tb, 'name'):
tb.data(self.name)
for tp in self.tps:
tp.build_tree(tb)
return tb
def to_element(self):
return self.build_tree(TreeBuilder()).close()
@classmethod
def from_element(cls, element):
name_tag = element.find('name')
name = name_tag.text.encode('utf_8') if name_tag else None
tps = map(Turnpoint.from_element, element.findall('rtept'))
return cls(name, tps)
@classmethod
def from_file(cls, file):
element = parse(file)
return cls.from_element(element.find('/rte'))

@ -258,6 +258,47 @@ def salient(seq, epsilon=0):
return sorted(result)
def salient2(seq, epsilons):
def helper(start, stop):
if stop - start < 2:
return
delta = 0
left, right = start, stop
if seq[start] <= seq[stop]:
max_index = start
for i in xrange(start + 1, stop + 1):
if seq[i] > seq[max_index]:
max_index = i
elif seq[max_index] - seq[i] > delta:
left, right = max_index, i
delta = seq[max_index] - seq[i]
if seq[start] >= seq[stop]:
min_index = start
for i in xrange(start + 1, stop + 1):
if seq[i] < seq[min_index]:
min_index = i
elif seq[i] - seq[min_index] > delta:
left, right = min_index, i
delta = seq[i] - seq[min_index]
if delta >= epsilons[-1] and (left != start or right != stop):
for i, epsilon in enumerate(epsilons):
if delta < epsilon:
continue
if not left in result or result[left] > i:
result[left] = i
if not right in result or result[right] > i:
result[right] = i
helper(start, left)
helper(left, right)
helper(right, stop)
result = {}
if len(seq):
result[0] = 0
result[len(seq) - 1] = 0
helper(0, len(seq) - 1)
return result.items()
def datetime_floor(dt, delta):
if delta.seconds >= 3600:
return dt.replace(minute=0, second=0) \

@ -15,39 +15,108 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import with_statement
import datetime
import xml.etree.ElementTree
try:
from xml.etree.cElementTree import ElementTree, parse
except ImportError:
from xml.etree.ElementTree import ElementTree, parse
from coord import Coord
from etree import tag
import coord
class Turnpoint(object):
class RtePt(object):
def __init__(self, name, coord):
self.name = name
self.coord = coord
def __init__(self, etree):
self.fix = etree.findtext('fix').encode('utf_8')
self.name = etree.findtext('name').encode('utf_8')
lat = float(etree.get('lat'))
lon = float(etree.get('lon'))
ele = int(etree.findtext('ele')) if self.fix == '3d' else 0
dt = datetime.datetime.strptime(etree.findtext('time'),
def build_tree(self, tb):
attrs = {'lat': str(self.coord.lat), 'lon': str(self.coord.lon)}
with tag(tb, 'rtept', attrs):
if self.coord.ele:
with tag(tb, 'ele'):
tb.data('%d' % ele)
with tag(tb, 'name'):
tb.data(self.name)
with tag(tb, 'time'):
tb.data(self.coord.dt.strftime('%Y-%m-%dT%H:%M:%SZ'))
return tb
@classmethod
def from_element(cls, rtept):
name = rtept.findtext('name').encode('utf_8')
lat = float(rtept.get('lat'))
lon = float(rtept.get('lon'))
ele_tag = rtept.find('ele')
ele = int(ele_tag.text) if ele_tag else 0
dt = datetime.datetime.strptime(rtept.findtext('time'),
'%Y-%m-%dT%H:%M:%SZ')
self.coord = coord.Coord.deg(lat, lon, ele, dt)
coord = Coord.deg(lat, lon, ele, dt)
return cls(name, coord)
class Route(object):
class Rte(object):
def __init__(self, name, league, distance, multiplier, score, circuit, tps):
self.name = name
self.league = league
self.distance = distance
self.multiplier = multiplier
self.score = score
self.circuit = circuit
self.tps = tps
def __init__(self, etree):
self.name = etree.findtext('name').encode('utf_8')
self.league = etree.findtext('extensions/league').encode('utf_8')
self.distance = float(etree.findtext('extensions/distance'))
self.multiplier = float(etree.findtext('extensions/multiplier'))
self.score = float(etree.findtext('extensions/score'))
self.circuit = not etree.find('extensions/circuit') is None
self.rtepts = [RtePt(rtept) for rtept in etree.findall('rtept')]
def build_tree(self, tb):
with tag(tb, 'rte'):
with tag(tb, 'name'):
tb.data(self.name)
with tag(tb, 'extensions'):
with tag(tb, 'league'):
tb.data(self.league)
with tag(tb, 'distance'):
tb.data(str(self.distance))
with tag(tb, 'multiplier'):
tb.data('%.2f' % self.multiplier)
with tag(tb, 'score'):
tb.data(str(self.score))
if self.circuit:
with tag(tb, 'circuit'):
pass
for tp in self.tps:
tp.build_tree(tb)
return tb
@classmethod
def from_element(cls, rte):
name = rte.findtext('name').encode('utf_8')
league = rte.findtext('extensions/league').encode('utf_8')
distance = float(rte.findtext('extensions/distance'))
multiplier = float(rte.findtext('extensions/multiplier'))
score = float(rte.findtext('extensions/score'))
circuit = not rte.find('extensions/circuit') is None
tps = map(Turnpoint.from_element, rte.findall('rtept'))
return cls(name, league, distance, multiplier, score, circuit, tps)
class XC(object):
def __init__(self, file):
etree = xml.etree.ElementTree.parse(file)
self.rtes = [Rte(rte) for rte in etree.findall('/rte')]
def __init__(self, routes):
self.routes = routes
def build_tree(self, tb):
for route in self.routes:
route.build_tree(tb)
return tb
@classmethod
def from_element(cls, element):
routes = map(Route.from_element, element.findall('/rte'))
return cls(routes)
@classmethod
def from_file(cls, file):
element = parse(file)
return cls.from_element(element)

Loading…
Cancel
Save