Refactor into single module.

debian-sid
Tom Payne 15 years ago
parent 488a339a32
commit 5d9205f445

@ -1,10 +0,0 @@
class OpenStruct(object):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def __iter__(self):
return self.__dict__.iteritems()
def __repr__(self):
return 'OpenStruct(%s)' % ', '.join('%s=%s' % (key, repr(value)) for key, value in self)

@ -1,2 +0,0 @@
class TimeSeries(list):
pass

@ -1,43 +0,0 @@
class Bounds(object):
def __init__(self, min, max=None):
self.min = min
self.max = max or min
def __repr__(self):
return 'Bounds(%(min)s, %(max)s)' % self.__dict__
def merge(self, value):
if isinstance(value, Bounds):
if value.min < self.min:
self.min = value.min
if value.max > self.max:
self.max = value.max
else:
if value < self.min:
self.min = value
if value > self.max:
self.max = value
def tuple(self):
return (self.min, self.max)
class BoundsSet(object):
def merge(self, other):
for key, value in other.__dict__.items():
if hasattr(self, key):
getattr(self, key).merge(value)
else:
setattr(self, key, Bounds(value.min, value.max))
def bounds(seq):
min = max = seq[0]
for i in xrange(1, len(seq)):
if seq[i] < min:
min = seq[i]
if seq[i] > max:
max = seq[i]
return Bounds(min, max)

@ -1,22 +1,16 @@
#!/usr/bin/python
import datetime
import fileinput
import optparse
import sys
from bounds import Bounds, BoundsSet
import gradient
import igc
import kml
import kmz
from OpenStruct import OpenStruct
import scale
from track import Hints, Stock
import igc2kmz
import igc2kmz.igc
import igc2kmz.kmz
def add_track(option, opt_str, value, parser, make_track, **kwargs):
track = make_track(value)
hints = Hints()
hints = igc2kmz.Hints()
for attr in 'color glider_type pilot_name'.split(' '):
if not getattr(parser.values, attr) is None:
setattr(hints, attr, getattr(parser.values, attr))
@ -32,7 +26,7 @@ def main(argv):
parser.add_option('-z', '--timezone-offset', metavar='INTEGER', type='int')
group = optparse.OptionGroup(parser, 'Per-track options')
group.add_option('-c', '--color', dest='color', metavar='COLOR')
group.add_option('-i', '--igc', action='callback', callback=add_track, dest='tracks_and_hints', metavar='FILENAME', nargs=1, type='string', callback_args=(lambda value: igc.IGC(value).track(),))
group.add_option('-i', '--igc', action='callback', callback=add_track, dest='tracks_and_hints', metavar='FILENAME', nargs=1, type='string', callback_args=(lambda value: igc2kmz.igc.IGC(value).track(),))
group.add_option('-g', '--glider-type', dest='glider_type', metavar='STRING')
group.add_option('-p', '--pilot-name', dest='pilot_name', metavar='STRING')
group.add_option('-w', '--width', dest='width', metavar='INTEGER', type='int')
@ -44,32 +38,12 @@ def main(argv):
raise RuntimeError # FIXME
if len(args) != 1:
raise RuntimeError # FIXME
bounds = BoundsSet()
for track, hints in options.tracks_and_hints:
track.analyse()
bounds.merge(track.bounds)
globals = OpenStruct()
globals.stock = Stock()
globals.timezone_offset = datetime.timedelta(0, 3600 * options.timezone_offset)
globals.altitude_scale = scale.Scale(bounds.ele.tuple(), title='altitude', gradient=gradient.default)
globals.altitude_styles = []
for color in globals.altitude_scale.colors():
balloon_style = kml.BalloonStyle(text='$[description]')
icon_style = kml.IconStyle(kml.Icon.palette(4, 24), scale=0.5)
label_style = kml.LabelStyle(color=color)
globals.altitude_styles.append(kml.Style(balloon_style, icon_style, label_style))
globals.stock.kmz.add_roots(*globals.altitude_styles)
globals.climb_scale = scale.ZeroCenteredScale(bounds.climb.tuple(), title='climb', step=0.1, gradient=gradient.bilinear)
globals.speed_scale = scale.Scale(bounds.speed.tuple(), title='ground speed', gradient=gradient.default)
globals.time_scale = scale.TimeScale(bounds.time.tuple(), timezone_offset=globals.timezone_offset)
globals.progress_scale = scale.Scale((0.0, 1.0), title='progress', gradient=gradient.default)
globals.graph_width = 600
globals.graph_height = 300
result = kmz.kmz()
globals = igc2kmz.Globals(options, [track for track, hints in options.tracks_and_hints])
result = igc2kmz.kmz.kmz()
result.add_siblings(globals.stock.kmz)
for track, hints in options.tracks_and_hints:
hints.globals = globals
result.add_siblings(track.kmz(hints))
result.add_siblings(igc2kmz.track2kmz(track, hints))
result.write(options.output)
if __name__ == '__main__':

@ -0,0 +1,233 @@
import datetime
import numpy
import pygooglechart
import igc2kmz.color
import igc2kmz.kml
import igc2kmz.kmz
import igc2kmz.scale
import igc2kmz.util
class Stock(object):
def make_none_folder(self, visibility):
icon = kml.Icon(href=self.pixel_url)
overlay_xy = kml.overlayXY(x=0, y=0, xunits='fraction', yunits='fraction')
screen_xy = kml.screenXY(x=0, y=0, xunits='fraction', yunits='fraction')
size = kml.size(x=0, y=0, xunits='fraction', yunits='fraction')
screen_overlay = kml.ScreenOverlay(icon, overlay_xy, screen_xy, size, visibility=visibility)
folder = kml.Folder(screen_overlay, name='None', styleUrl=self.check_hide_children_style.url())
return kmz.kmz(folder)
def __init__(self):
self.kmz = kmz.kmz()
self.radio_folder_style = kml.Style(kml.ListStyle(listItemType='radioFolder'))
self.kmz.add_roots(self.radio_folder_style)
self.check_hide_children_style = kml.Style(kml.ListStyle(listItemType='checkHideChildren'))
self.kmz.add_roots(self.check_hide_children_style)
self.pixel_url = 'images/pixel.png'
self.kmz.add_files({self.pixel_url: open(self.pixel_url).read()})
self.visible_none_folder = self.make_none_folder(1)
self.invisible_none_folder = self.make_none_folder(0)
animation_icon_url = 'images/paraglider.png'
self.animation_icon = kml.Icon(href=animation_icon_url)
self.kmz.add_files({animation_icon_url: open(animation_icon_url).read()})
class Globals(object):
def __init__(self, options, tracks):
self.stock = igc2kmz.Stock()
self.bounds = igc2kmz.util.BoundsSet()
for track in tracks:
self.bounds.merge(track.bounds)
self.timezone_offset = datetime.timedelta(0, 3600 * options.timezone_offset)
self.altitude_scale = igc2kmz.scale.Scale(self.bounds.ele.tuple(), title='altitude', gradient=igc2kmz.color.default_gradient)
self.altitude_styles = []
for color in self.altitude_scale.colors():
balloon_style = kml.BalloonStyle(text='$[description]')
icon_style = kml.IconStyle(kml.Icon.palette(4, 24), scale=0.5)
label_style = kml.LabelStyle(color=color)
self.altitude_styles.append(kml.Style(balloon_style, icon_style, label_style))
self.stock.kmz.add_roots(*self.altitude_styles)
self.climb_scale = igc2kmz.scale.ZeroCenteredScale(self.bounds.climb.tuple(), title='climb', step=0.1, gradient=igc2kmz.color.bilinear_gradient)
self.speed_scale = igc2kmz.scale.Scale(self.bounds.speed.tuple(), title='ground speed', gradient=igc2kmz.color.default_gradient)
self.time_scale = igc2kmz.scale.TimeScale(self.bounds.time.tuple(), timezone_offset=self.timezone_offset)
self.progress_scale = igc2kmz.scale.Scale((0.0, 1.0), title='progress', gradient=igc2kmz.color.default_gradient)
self.graph_width = 600
self.graph_height = 300
class Hints(object):
def __init__(self):
self.altitude_mode = 'absolute'
self.color = 'ff0000ff'
self.width = 2
def make_solid_track(track, hints, style, altitude_mode, extrude=None, **folder_options):
line_string = kml.LineString(coordinates=track.coords, altitudeMode=altitude_mode)
if extrude:
line_string.add(extrude=1)
placemark = kml.Placemark(style, line_string)
folder_options['styleUrl'] = hints.globals.stock.check_hide_children_style.url()
return kmz.kmz(kml.Folder(placemark, **folder_options))
def make_scale_chart(track, hints, scale):
chart = pygooglechart.SimpleLineChart(50, 200, x_range=(0, 1), y_range=scale.range)
chart.fill_solid(pygooglechart.Chart.BACKGROUND, 'ffffff00')
chart.fill_solid(pygooglechart.Chart.CHART, 'ffffffcc')
for i in xrange(0, 32 + 1):
y = i * (scale.range[1] - scale.range[0]) / 32 + scale.range[0]
chart.add_data([y, y])
chart.set_line_style(i, 0)
for i in xrange(0, 32):
r, g, b, a = scale.color((i * (scale.range[1] - scale.range[0]) + 0.5) / 32 + scale.range[0])
color = '%02x%02x%02x' % (255 * r, 255 * g, 255 * b)
chart.add_fill_range(color, i, i + 1)
axis_index = chart.set_axis_range(pygooglechart.Axis.LEFT, scale.range[0], scale.range[1])
chart.set_axis_style(axis_index, 'ffffff')
return chart
def make_colored_track(track, hints, values, scale, altitude_mode, **folder_options):
folder = kml.Folder(name='Colored by %s' % scale.title, styleUrl=hints.globals.stock.check_hide_children_style.url(), **folder_options)
styles = [kml.Style(kml.LineStyle(color=color, width=hints.width)) for color in scale.colors()]
discrete_values = map(scale.discretize, values)
for start, end in util.runs(discrete_values):
line_string = kml.LineString(coordinates=track.coords[start:end + 1], altitudeMode=hints.altitude_mode)
style_url = kml.styleUrl(styles[discrete_values[start]].url())
placemark = kml.Placemark(style_url, line_string)
folder.add(placemark)
icon = kml.Icon(href=kml.CDATA(make_scale_chart(track, hints, scale).get_url()))
overlay_xy = kml.overlayXY(x=0, y=1, xunits='fraction', yunits='fraction')
screen_xy = kml.screenXY(x=0, y=1, xunits='fraction', yunits='fraction')
size = kml.size(x=0, y=0, xunits='fraction', yunits='fraction')
screen_overlay = kml.ScreenOverlay(icon, overlay_xy, screen_xy, size)
folder.add(screen_overlay)
return kmz.kmz(folder).add_roots(*styles)
def make_track_folder(track, hints):
folder = kmz.kmz(kml.Folder(name='Track', open=1, styleUrl=hints.globals.stock.radio_folder_style.url()))
folder.add(hints.globals.stock.invisible_none_folder)
if track.elevation_data:
folder.add(make_colored_track(track, hints, track.ele, hints.globals.altitude_scale, 'absolute', visibility=0))
folder.add(make_colored_track(track, hints, track.climb, hints.globals.climb_scale, 'absolute'))
folder.add(make_colored_track(track, hints, track.speed, hints.globals.speed_scale, hints.altitude_mode, visibility=not track.elevation_data))
folder.add(make_colored_track(track, hints, track.progress, hints.globals.progress_scale, hints.altitude_mode, visibility=0))
folder.add(make_colored_track(track, hints, track.thermal, hints.globals.progress_scale, hints.altitude_mode, visibility=0))
folder.add(make_solid_track(track, hints, kml.Style(kml.LineStyle(color=hints.color, width=hints.width)), hints.altitude_mode, name='Solid color', visibility=0))
return folder
def make_shadow_folder(track, hints):
if not track.elevation_data:
return kmz.kmz()
folder = kmz.kmz(kml.Folder(name='Shadow', open=1, styleUrl=hints.globals.stock.radio_folder_style.url()))
folder.add(hints.globals.stock.invisible_none_folder)
folder.add(make_solid_track(track, hints, kml.Style(kml.LineStyle(color='ff000000', width=1)), 'clampToGround', name='Normal', visibility=1))
folder.add(make_solid_track(track, hints, kml.Style(kml.LineStyle(color='00000000', width=1), kml.PolyStyle(color='80000000')), 'absolute', True, name='Extrude', visibility=0))
folder.add(make_solid_track(track, hints, kml.Style(kml.LineStyle(color=hints.color, width=hints.width)), 'clampToGround', name='Solid color', visibility=0))
return folder
def make_animation(track, hints):
style = kml.Style(kml.IconStyle(hints.globals.stock.animation_icon, color=hints.color, scale=0.5))
folder = kml.Folder(style, name='Animation', open=0, styleUrl=hints.globals.stock.check_hide_children_style.url())
point = kml.Point(coordinates=[track.coords[0]], altitudeMode=hints.altitude_mode)
timespan = kml.TimeSpan(end=kml.dateTime(track.times[0]))
placemark = kml.Placemark(point, timespan, styleUrl=style.url())
folder.add(placemark)
for i in xrange(1, len(track.coords)):
point = kml.Point(coordinates=[track.coords[i - 1].halfway_to(track.coords[i])], altitudeMode=hints.altitude_mode)
timespan = kml.TimeSpan(begin=kml.dateTime(track.times[i - 1]), end=kml.dateTime(track.times[i]))
placemark = kml.Placemark(point, timespan, styleUrl=style.url())
folder.add(placemark)
point = kml.Point(coordinates=[track.coords[-1]], altitudeMode=hints.altitude_mode)
timespan = kml.TimeSpan(begin=kml.dateTime(track.times[-1]))
placemark = kml.Placemark(point, timespan, styleUrl=style.url())
folder.add(placemark)
return kmz.kmz(folder)
def make_placemark(track, coord, altitudeMode=None, name=None, styleUrl=None):
point = kml.Point(coordinates=[coord], altitudeMode=altitudeMode)
placemark = kml.Placemark(point, kml.Snippet(), name=name, styleUrl=styleUrl)
return placemark
def make_altitude_marks_folder(track, hints):
if track.elevation_data:
folder = kml.Folder(name='Altitude marks', styleUrl=hints.globals.stock.check_hide_children_style.url())
for index in util.salient([c.ele for c in track.coords], 100):
coord = track.coords[index]
folder.add(make_placemark(track, coord, altitudeMode='absolute', name='%dm' % coord.ele, styleUrl=hints.globals.altitude_styles[hints.globals.altitude_scale.discretize(coord.ele)].url()))
return kmz.kmz(folder)
else:
return kmz.kmz()
def make_graph_chart(track, hints, values, scale):
chart = pygooglechart.XYLineChart(hints.globals.graph_width, hints.globals.graph_height, x_range=hints.globals.time_scale.range, y_range=scale.range)
chart.fill_solid(pygooglechart.Chart.BACKGROUND, 'ffffff00')
chart.fill_solid(pygooglechart.Chart.CHART, 'ffffffcc')
axis_index = chart.set_axis_labels(pygooglechart.Axis.BOTTOM, hints.globals.time_scale.labels)
chart.set_axis_positions(axis_index, hints.globals.time_scale.positions)
chart.set_axis_style(axis_index, 'ffffff')
axis_index = chart.set_axis_range(pygooglechart.Axis.LEFT, scale.range[0], scale.range[1])
chart.set_axis_style(axis_index, 'ffffff')
chart.set_grid(hints.globals.time_scale.grid_step, scale.grid_step, 2, 2)
y = hints.globals.graph_height * (numpy.array(values) - scale.range[0]) / (scale.range[1] - scale.range[0])
indexes = util.incremental_douglas_peucker(hints.time_positions, y, 1, 450)
chart.add_data([track.coords.t[i] for i in indexes])
chart.add_data([values[i] for i in indexes])
return chart
def make_graph(track, hints, values, scale):
icon = kml.Icon(href=kml.CDATA(make_graph_chart(track, hints, values, scale).get_url()))
overlay_xy = kml.overlayXY(x=0, y=0, xunits='fraction', yunits='fraction')
screen_xy = kml.screenXY(x=0, y=16, xunits='fraction', yunits='pixels')
size = kml.size(x=0, y=0, xunits='fraction', yunits='fraction')
screen_overlay = kml.ScreenOverlay(icon, overlay_xy, screen_xy, size)
folder = kml.Folder(screen_overlay, name=scale.title.capitalize(), styleUrl=hints.globals.stock.check_hide_children_style.url(), visibility=0)
return folder
def make_graphs_folder(track, hints):
folder = kmz.kmz(kml.Folder(name='Graphs', open=1, styleUrl=hints.globals.stock.radio_folder_style.url()))
folder.add(hints.globals.stock.visible_none_folder)
folder.add(make_graph(track, hints, [c.ele for c in track.coords], hints.globals.altitude_scale))
#folder.add(make_graph(track, hints, track.climb, hints.globals.climb_scale))
#folder.add(make_graph(track, hints, track.speed, hints.globals.speed_scale))
return folder
def track2kmz(track, hints):
folder = kmz.kmz(kml.Folder(name=track.meta.name, open=1))
rows = []
if track.meta.pilot_name:
rows.append(('Pilot name', track.meta.pilot_name))
if track.meta.glider_type:
rows.append(('Glider type', track.meta.glider_type))
if track.meta.glider_id:
rows.append(('Glider ID', track.meta.glider_id))
rows.append(('Take-off time', (track.times[0] + hints.globals.timezone_offset).strftime('%H:%M:%S')))
rows.append(('Landing time', (track.times[-1] + hints.globals.timezone_offset).strftime('%H:%M:%S')))
hour, seconds = divmod((track.times[-1] - track.times[0]).seconds, 3600)
minute, second = divmod(seconds, 60)
rows.append(('Duration', '%d:%02d:%02d' % (hour, minute, second)))
if track.elevation_data:
rows.append(('Take-off altitude', '%dm' % track.coords[0].ele))
rows.append(('Maximum altitude', '%dm' % track.bounds.ele.max))
rows.append(('Minimum altitude', '%dm' % track.bounds.ele.min))
rows.append(('Landing altitude', '%dm' % track.coords[-1].ele))
rows.append(('Total altitude gain', '%dm' % track.total_dz_positive))
rows.append(('Maximum altitude gain', '%dm' % track.max_dz_positive))
rows.append(('Maximum climb', '%.1fm/s' % track.bounds.climb.max))
rows.append(('Maximum sink', '%.1fm/s' % track.bounds.climb.min))
rows.append(('Maximum speed', '%.1fkm/h' % track.bounds.speed.max))
folder.add(kml.description(kml.CDATA('<table>%s</table>' % ''.join('<tr><th align="right">%s</th><td>%s</td></tr>' % row for row in rows))))
snippet = [track.meta.pilot_name, track.meta.glider_type, (track.times[0] + hints.globals.timezone_offset).strftime('%Y-%m-%d')]
folder.add(kml.Snippet(', '.join(s for s in snippet if s)))
hints.time_positions = hints.globals.graph_width * (numpy.array(track.coords.t) - hints.globals.time_scale.range[0]) / (hints.globals.time_scale.range[1] - hints.globals.time_scale.range[0])
folder.add(make_animation(track, hints))
folder.add(make_track_folder(track, hints))
folder.add(make_shadow_folder(track, hints))
folder.add(make_altitude_marks_folder(track, hints))
folder.add(make_graphs_folder(track, hints))
return folder

@ -49,7 +49,7 @@ def hsv_to_rgb(hsv):
return (v, p, q)
def grayscale(value):
def grayscale_gradient(value):
"""Return a gradient from black to white."""
if value < 0.0:
return (1.0, 0.0, 0.0, 0.0)
@ -59,7 +59,7 @@ def grayscale(value):
return (1.0, value, value, value)
def default(value):
def default_gradient(value):
"""Return a gradient from blue to green to red."""
if value < 0.0:
return hsl_to_rgba((2.0 / 3.0, 1.0, 0.5))
@ -70,7 +70,7 @@ def default(value):
return hsl_to_rgba((h, 1.0, 0.5))
def bilinear(value):
def bilinear_gradient(value):
"""Return a bilinear gradient from blue to green to red."""
if value < 0.0:
h = 2.0 / 3.0

@ -4,10 +4,9 @@ import time
import re
import sys
import coord
import track
from TimeSeries import TimeSeries
from OpenStruct import OpenStruct
import igc2kmz.coord
import igc2kmz.util
import igc2kmz.track
A_RECORD_RE = re.compile(r'A(.*)\Z')
@ -180,18 +179,18 @@ class IGC(object):
break
else:
ele = 'alt'
coords = TimeSeries()
coords = igc2kmz.util.TimeSeries()
for record in self.records:
if isinstance(record, BRecord):
coords.append(coord.Coord(record.lat, record.lon, getattr(record, ele)))
coords.append(igc2kmz.coord.Coord(record.lat, record.lon, getattr(record, ele)))
times.append(record.dt)
t.append(int(time.mktime(record.dt.timetuple())))
coords.t = t
meta = OpenStruct(name=os.path.basename(self.filename), pilot_name=None, glider_type=None, glider_id=None)
meta = igc2kmz.util.OpenStruct(name=os.path.basename(self.filename), pilot_name=None, glider_type=None, glider_id=None)
if 'plt' in self.h and not NOT_SET_RE.match(self.h['plt']):
meta.pilot_name = self.h['plt'].strip()
if 'gty' in self.h and not NOT_SET_RE.match(self.h['gty']):
meta.glider_type = self.h['gty'].strip()
if 'gid' in self.h and not NOT_SET_RE.match(self.h['gid']):
meta.glider_id = self.h['gid'].strip()
return track.Track(meta, times, coords)
return igc2kmz.track.Track(meta, times, coords)

@ -0,0 +1,88 @@
import igc2kmz.util
class Track(object):
def __init__(self, meta, times, coords):
self.meta = meta
self.times = times
self.coords = coords
self.analyse(20)
def analyse(self, dt):
n = len(self.coords)
self.bounds = igc2kmz.util.BoundsSet()
self.bounds.ele = igc2kmz.util.Bounds(self.coords[0].ele)
for coord in self.coords:
self.bounds.ele.merge(coord.ele)
self.bounds.time = igc2kmz.util.Bounds(self.times[0], self.times[-1])
self.elevation_data = self.bounds.ele.min != 0 or self.bounds.ele.max != 0
self.s = [0.0]
for i in xrange(1, n):
self.s.append(self.s[i - 1] + self.coords[i - 1].distance_to(self.coords[i]))
self.ele = []
for i in xrange(1, n):
self.ele.append((self.coords[i - 1].ele + self.coords[i].ele) / 2.0)
self.total_dz_positive = 0
self.max_dz_positive = 0
min_ele = self.coords[0].ele
for i in xrange(1, n):
dz = self.coords[i].ele - self.coords[i - 1].ele
if dz > 0:
self.total_dz_positive += dz
if self.coords[i].ele < min_ele:
min_ele = self.coords[i].ele
elif self.coords[i].ele - min_ele > self.max_dz_positive:
self.max_dz_positive = self.coords[i].ele - min_ele
self.speed = []
self.climb = []
self.progress = []
self.thermal = []
i0 = i1 = 0
for i in xrange(1, n):
t0 = (self.coords.t[i - 1] + self.coords.t[i]) / 2 - dt / 2
while self.coords.t[i0] <= t0:
i0 += 1
if i0 == 0:
coord0 = self.coords[0]
s0 = self.s[0]
else:
delta0 = float(t0 - self.coords.t[i0 - 1]) / (self.coords.t[i0] - self.coords.t[i0 - 1])
coord0 = self.coords[i0 - 1].interpolate(self.coords[i0], delta0)
s0 = (1.0 - delta0) * self.s[i0 - 1] + delta0 * self.s[i0]
t1 = t0 + dt
while i1 < n and self.coords.t[i1] < t1:
i1 += 1
if i1 == n:
coord1 = self.coords[n - 1]
s1 = self.s[n - 1]
else:
delta1 = float(t1 - self.coords.t[i1 - 1]) / (self.coords.t[i1] - self.coords.t[i1 - 1])
coord1 = self.coords[i1 - 1].interpolate(self.coords[i1], delta1)
s1 = (1.0 - delta1) * self.s[i1 - 1] + delta1 * self.s[i1]
ds = s1 - s0
dz = coord1.ele - coord0.ele
dp = coord0.distance_to(coord1)
climb = dz / dt
if ds == 0.0:
progress = 0.0
elif dp > ds:
progress = 1.0
else:
progress = dp / ds
if climb >= 0.0:
if progress < 0.9:
thermal = 1.0
else:
thermal = 0.75
else:
if progress < 0.9:
thermal = 0.5
else:
thermal = 0.0
self.speed.append(3.6 * ds / dt)
self.climb.append(dz / dt)
self.progress.append(progress)
self.thermal.append(thermal)
self.bounds.speed = igc2kmz.util.bounds(self.speed)
self.bounds.climb = igc2kmz.util.bounds(self.climb)

@ -3,6 +3,67 @@ import math
import sys
class Bounds(object):
def __init__(self, min, max=None):
self.min = min
self.max = max or min
def __repr__(self):
return 'Bounds(%(min)s, %(max)s)' % self.__dict__
def merge(self, value):
if isinstance(value, Bounds):
if value.min < self.min:
self.min = value.min
if value.max > self.max:
self.max = value.max
else:
if value < self.min:
self.min = value
if value > self.max:
self.max = value
def tuple(self):
return (self.min, self.max)
class BoundsSet(object):
def merge(self, other):
for key, value in other.__dict__.items():
if hasattr(self, key):
getattr(self, key).merge(value)
else:
setattr(self, key, Bounds(value.min, value.max))
class OpenStruct(object):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def __iter__(self):
return self.__dict__.iteritems()
def __repr__(self):
return 'OpenStruct(%s)' % ', '.join('%s=%s' % (key, repr(value)) for key, value in self)
class TimeSeries(list):
pass
def bounds(seq):
min = max = seq[0]
for i in xrange(1, len(seq)):
if seq[i] < min:
min = seq[i]
if seq[i] > max:
max = seq[i]
return Bounds(min, max)
def runs(seq):
generator = enumerate(seq)
try:

@ -5,12 +5,12 @@ import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
import igc
import igc2kmz.igc
def main(argv):
for arg in argv[1:]:
try:
igc.IGC(arg)
igc2kmz.igc.IGC(arg)
except igc.SyntaxError, line:
print "%s: %s" % (arg, line)

@ -4,7 +4,7 @@ import unittest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from lib import find_first_ge, salient
from igc2kmz.util import find_first_ge, salient
class TestFindFirstGE(unittest.TestCase):

@ -1,300 +0,0 @@
from cStringIO import StringIO
import datetime
import itertools
import math
import urllib
import sys
import time
import numpy
import pygooglechart
from bounds import bounds, Bounds, BoundsSet
import kml
import kmz
import lib
from OpenStruct import OpenStruct
class Stock(object):
def make_none_folder(self, visibility):
icon = kml.Icon(href=self.pixel_url)
overlay_xy = kml.overlayXY(x=0, y=0, xunits='fraction', yunits='fraction')
screen_xy = kml.screenXY(x=0, y=0, xunits='fraction', yunits='fraction')
size = kml.size(x=0, y=0, xunits='fraction', yunits='fraction')
screen_overlay = kml.ScreenOverlay(icon, overlay_xy, screen_xy, size, visibility=visibility)
folder = kml.Folder(screen_overlay, name='None', styleUrl=self.check_hide_children_style.url())
return kmz.kmz(folder)
def __init__(self):
self.kmz = kmz.kmz()
self.radio_folder_style = kml.Style(kml.ListStyle(listItemType='radioFolder'))
self.kmz.add_roots(self.radio_folder_style)
self.check_hide_children_style = kml.Style(kml.ListStyle(listItemType='checkHideChildren'))
self.kmz.add_roots(self.check_hide_children_style)
self.pixel_url = 'images/pixel.png'
self.kmz.add_files({self.pixel_url: open(self.pixel_url).read()})
self.visible_none_folder = self.make_none_folder(1)
self.invisible_none_folder = self.make_none_folder(0)
animation_icon_url = 'images/paraglider.png'
self.animation_icon = kml.Icon(href=animation_icon_url)
self.kmz.add_files({animation_icon_url: open(animation_icon_url).read()})
class Hints(object):
def __init__(self):
self.altitude_mode = 'absolute'
self.color = 'ff0000ff'
self.width = 2
class Track(object):
def __init__(self, meta, times, coords):
self.meta = meta
self.times = times
self.coords = coords
self.bounds = BoundsSet()
self.bounds.ele = Bounds(self.coords[0].ele)
for coord in self.coords:
self.bounds.ele.merge(coord.ele)
self.bounds.time = Bounds(self.times[0], self.times[-1])
self.elevation_data = self.bounds.ele.min != 0 or self.bounds.ele.max != 0
self.s = [0.0]
for i in xrange(1, len(self.coords)):
self.s.append(self.s[i - 1] + self.coords[i - 1].distance_to(self.coords[i]))
def analyse(self, dt=20):
n = len(self.coords)
self.ele = []
for i in xrange(1, n):
self.ele.append((self.coords[i - 1].ele + self.coords[i].ele) / 2.0)
self.total_dz_positive = 0
self.max_dz_positive = 0
min_ele = self.coords[0].ele
for i in xrange(1, n):
dz = self.coords[i].ele - self.coords[i - 1].ele
if dz > 0:
self.total_dz_positive += dz
if self.coords[i].ele < min_ele:
min_ele = self.coords[i].ele
elif self.coords[i].ele - min_ele > self.max_dz_positive:
self.max_dz_positive = self.coords[i].ele - min_ele
self.speed = []
self.climb = []
self.progress = []
self.thermal = []
i0 = i1 = 0
for i in xrange(1, n):
t0 = (self.coords.t[i - 1] + self.coords.t[i]) / 2 - dt / 2
while self.coords.t[i0] <= t0:
i0 += 1
if i0 == 0:
coord0 = self.coords[0]
s0 = self.s[0]
else:
delta0 = float(t0 - self.coords.t[i0 - 1]) / (self.coords.t[i0] - self.coords.t[i0 - 1])
coord0 = self.coords[i0 - 1].interpolate(self.coords[i0], delta0)
s0 = (1.0 - delta0) * self.s[i0 - 1] + delta0 * self.s[i0]
t1 = t0 + dt
while i1 < n and self.coords.t[i1] < t1:
i1 += 1
if i1 == n:
coord1 = self.coords[n - 1]
s1 = self.s[n - 1]
else:
delta1 = float(t1 - self.coords.t[i1 - 1]) / (self.coords.t[i1] - self.coords.t[i1 - 1])
coord1 = self.coords[i1 - 1].interpolate(self.coords[i1], delta1)
s1 = (1.0 - delta1) * self.s[i1 - 1] + delta1 * self.s[i1]
ds = s1 - s0
dz = coord1.ele - coord0.ele
dp = coord0.distance_to(coord1)
climb = dz / dt
if ds == 0.0:
progress = 0.0
elif dp > ds:
progress = 1.0
else:
progress = dp / ds
if climb >= 0.0:
if progress < 0.9:
thermal = 1.0
else:
thermal = 0.75
else:
if progress < 0.9:
thermal = 0.5
else:
thermal = 0.0
self.speed.append(3.6 * ds / dt)
self.climb.append(dz / dt)
self.progress.append(progress)
self.thermal.append(thermal)
self.bounds.speed = bounds(self.speed)
self.bounds.climb = bounds(self.climb)
def make_solid_track(self, hints, style, altitude_mode, extrude=None, **folder_options):
line_string = kml.LineString(coordinates=self.coords, altitudeMode=altitude_mode)
if extrude:
line_string.add(extrude=1)
placemark = kml.Placemark(style, line_string)
folder_options['styleUrl'] = hints.globals.stock.check_hide_children_style.url()
return kmz.kmz(kml.Folder(placemark, **folder_options))
def make_scale_chart(self, hints, scale):
chart = pygooglechart.SimpleLineChart(50, 200, x_range=(0, 1), y_range=scale.range)
chart.fill_solid(pygooglechart.Chart.BACKGROUND, 'ffffff00')
chart.fill_solid(pygooglechart.Chart.CHART, 'ffffffcc')
for i in xrange(0, 32 + 1):
y = i * (scale.range[1] - scale.range[0]) / 32 + scale.range[0]
chart.add_data([y, y])
chart.set_line_style(i, 0)
for i in xrange(0, 32):
r, g, b, a = scale.color((i * (scale.range[1] - scale.range[0]) + 0.5) / 32 + scale.range[0])
color = '%02x%02x%02x' % (255 * r, 255 * g, 255 * b)
chart.add_fill_range(color, i, i + 1)
axis_index = chart.set_axis_range(pygooglechart.Axis.LEFT, scale.range[0], scale.range[1])
chart.set_axis_style(axis_index, 'ffffff')
return chart
def make_colored_track(self, hints, values, scale, altitude_mode, **folder_options):
folder = kml.Folder(name='Colored by %s' % scale.title, styleUrl=hints.globals.stock.check_hide_children_style.url(), **folder_options)
styles = [kml.Style(kml.LineStyle(color=color, width=hints.width)) for color in scale.colors()]
discrete_values = map(scale.discretize, values)
for start, end in lib.runs(discrete_values):
line_string = kml.LineString(coordinates=self.coords[start:end + 1], altitudeMode=hints.altitude_mode)
style_url = kml.styleUrl(styles[discrete_values[start]].url())
placemark = kml.Placemark(style_url, line_string)
folder.add(placemark)
icon = kml.Icon(href=kml.CDATA(self.make_scale_chart(hints, scale).get_url()))
overlay_xy = kml.overlayXY(x=0, y=1, xunits='fraction', yunits='fraction')
screen_xy = kml.screenXY(x=0, y=1, xunits='fraction', yunits='fraction')
size = kml.size(x=0, y=0, xunits='fraction', yunits='fraction')
screen_overlay = kml.ScreenOverlay(icon, overlay_xy, screen_xy, size)
folder.add(screen_overlay)
return kmz.kmz(folder).add_roots(*styles)
def make_track_folder(self, hints):
folder = kmz.kmz(kml.Folder(name='Track', open=1, styleUrl=hints.globals.stock.radio_folder_style.url()))
folder.add(hints.globals.stock.invisible_none_folder)
if self.elevation_data:
folder.add(self.make_colored_track(hints, self.ele, hints.globals.altitude_scale, 'absolute', visibility=0))
folder.add(self.make_colored_track(hints, self.climb, hints.globals.climb_scale, 'absolute'))
folder.add(self.make_colored_track(hints, self.speed, hints.globals.speed_scale, hints.altitude_mode, visibility=not self.elevation_data))
folder.add(self.make_colored_track(hints, self.progress, hints.globals.progress_scale, hints.altitude_mode, visibility=0))
folder.add(self.make_colored_track(hints, self.thermal, hints.globals.progress_scale, hints.altitude_mode, visibility=0))
folder.add(self.make_solid_track(hints, kml.Style(kml.LineStyle(color=hints.color, width=hints.width)), hints.altitude_mode, name='Solid color', visibility=0))
return folder
def make_shadow_folder(self, hints):
if not self.elevation_data:
return kmz.kmz()
folder = kmz.kmz(kml.Folder(name='Shadow', open=1, styleUrl=hints.globals.stock.radio_folder_style.url()))
folder.add(hints.globals.stock.invisible_none_folder)
folder.add(self.make_solid_track(hints, kml.Style(kml.LineStyle(color='ff000000', width=1)), 'clampToGround', name='Normal', visibility=1))
folder.add(self.make_solid_track(hints, kml.Style(kml.LineStyle(color='00000000', width=1), kml.PolyStyle(color='80000000')), 'absolute', True, name='Extrude', visibility=0))
folder.add(self.make_solid_track(hints, kml.Style(kml.LineStyle(color=hints.color, width=hints.width)), 'clampToGround', name='Solid color', visibility=0))
return folder
def make_animation(self, hints):
style = kml.Style(kml.IconStyle(hints.globals.stock.animation_icon, color=hints.color, scale=0.5))
folder = kml.Folder(style, name='Animation', open=0, styleUrl=hints.globals.stock.check_hide_children_style.url())
point = kml.Point(coordinates=[self.coords[0]], altitudeMode=hints.altitude_mode)
timespan = kml.TimeSpan(end=kml.dateTime(self.times[0]))
placemark = kml.Placemark(point, timespan, styleUrl=style.url())
folder.add(placemark)
for i in xrange(1, len(self.coords)):
point = kml.Point(coordinates=[self.coords[i - 1].halfway_to(self.coords[i])], altitudeMode=hints.altitude_mode)
timespan = kml.TimeSpan(begin=kml.dateTime(self.times[i - 1]), end=kml.dateTime(self.times[i]))
placemark = kml.Placemark(point, timespan, styleUrl=style.url())
folder.add(placemark)
point = kml.Point(coordinates=[self.coords[-1]], altitudeMode=hints.altitude_mode)
timespan = kml.TimeSpan(begin=kml.dateTime(self.times[-1]))
placemark = kml.Placemark(point, timespan, styleUrl=style.url())
folder.add(placemark)
return kmz.kmz(folder)
def make_placemark(self, coord, altitudeMode=None, name=None, styleUrl=None):
point = kml.Point(coordinates=[coord], altitudeMode=altitudeMode)
placemark = kml.Placemark(point, kml.Snippet(), name=name, styleUrl=styleUrl)
return placemark
def make_altitude_marks_folder(self, hints):
if self.elevation_data:
folder = kml.Folder(name='Altitude marks', styleUrl=hints.globals.stock.check_hide_children_style.url())
for index in lib.salient([c.ele for c in self.coords], 100):
coord = self.coords[index]
folder.add(self.make_placemark(coord, altitudeMode='absolute', name='%dm' % coord.ele, styleUrl=hints.globals.altitude_styles[hints.globals.altitude_scale.discretize(coord.ele)].url()))
return kmz.kmz(folder)
else:
return kmz.kmz()
def make_graph_chart(self, hints, values, scale):
chart = pygooglechart.XYLineChart(hints.globals.graph_width, hints.globals.graph_height, x_range=hints.globals.time_scale.range, y_range=scale.range)
chart.fill_solid(pygooglechart.Chart.BACKGROUND, 'ffffff00')
chart.fill_solid(pygooglechart.Chart.CHART, 'ffffffcc')
axis_index = chart.set_axis_labels(pygooglechart.Axis.BOTTOM, hints.globals.time_scale.labels)
chart.set_axis_positions(axis_index, hints.globals.time_scale.positions)
chart.set_axis_style(axis_index, 'ffffff')
axis_index = chart.set_axis_range(pygooglechart.Axis.LEFT, scale.range[0], scale.range[1])
chart.set_axis_style(axis_index, 'ffffff')
chart.set_grid(hints.globals.time_scale.grid_step, scale.grid_step, 2, 2)
y = hints.globals.graph_height * (numpy.array(values) - scale.range[0]) / (scale.range[1] - scale.range[0])
indexes = lib.incremental_douglas_peucker(hints.time_positions, y, 1, 450)
chart.add_data([self.coords.t[i] for i in indexes])
chart.add_data([values[i] for i in indexes])
return chart
def make_graph(self, hints, values, scale):
icon = kml.Icon(href=kml.CDATA(self.make_graph_chart(hints, values, scale).get_url()))
overlay_xy = kml.overlayXY(x=0, y=0, xunits='fraction', yunits='fraction')
screen_xy = kml.screenXY(x=0, y=16, xunits='fraction', yunits='pixels')
size = kml.size(x=0, y=0, xunits='fraction', yunits='fraction')
screen_overlay = kml.ScreenOverlay(icon, overlay_xy, screen_xy, size)
folder = kml.Folder(screen_overlay, name=scale.title.capitalize(), styleUrl=hints.globals.stock.check_hide_children_style.url(), visibility=0)
return folder
def make_graphs_folder(self, hints):
folder = kmz.kmz(kml.Folder(name='Graphs', open=1, styleUrl=hints.globals.stock.radio_folder_style.url()))
folder.add(hints.globals.stock.visible_none_folder)
folder.add(self.make_graph(hints, [c.ele for c in self.coords], hints.globals.altitude_scale))
#folder.add(self.make_graph(hints, self.climb, hints.globals.climb_scale))
#folder.add(self.make_graph(hints, self.speed, hints.globals.speed_scale))
return folder
def kmz(self, hints):
folder = kmz.kmz(kml.Folder(name=self.meta.name, open=1))
rows = []
if self.meta.pilot_name:
rows.append(('Pilot name', self.meta.pilot_name))
if self.meta.glider_type:
rows.append(('Glider type', self.meta.glider_type))
if self.meta.glider_id:
rows.append(('Glider ID', self.meta.glider_id))
rows.append(('Take-off time', (self.times[0] + hints.globals.timezone_offset).strftime('%H:%M:%S')))
rows.append(('Landing time', (self.times[-1] + hints.globals.timezone_offset).strftime('%H:%M:%S')))
hour, seconds = divmod((self.times[-1] - self.times[0]).seconds, 3600)
minute, second = divmod(seconds, 60)
rows.append(('Duration', '%d:%02d:%02d' % (hour, minute, second)))
if self.elevation_data:
rows.append(('Take-off altitude', '%dm' % self.coords[0].ele))
rows.append(('Maximum altitude', '%dm' % self.bounds.ele.max))
rows.append(('Minimum altitude', '%dm' % self.bounds.ele.min))
rows.append(('Landing altitude', '%dm' % self.coords[-1].ele))
rows.append(('Total altitude gain', '%dm' % self.total_dz_positive))
rows.append(('Maximum altitude gain', '%dm' % self.max_dz_positive))
rows.append(('Maximum climb', '%.1fm/s' % self.bounds.climb.max))
rows.append(('Maximum sink', '%.1fm/s' % self.bounds.climb.min))
rows.append(('Maximum speed', '%.1fkm/h' % self.bounds.speed.max))
folder.add(kml.description(kml.CDATA('<table>%s</table>' % ''.join('<tr><th align="right">%s</th><td>%s</td></tr>' % row for row in rows))))
snippet = [self.meta.pilot_name, self.meta.glider_type, (self.times[0] + hints.globals.timezone_offset).strftime('%Y-%m-%d')]
folder.add(kml.Snippet(', '.join(s for s in snippet if s)))
hints.time_positions = hints.globals.graph_width * (numpy.array(self.coords.t) - hints.globals.time_scale.range[0]) / (hints.globals.time_scale.range[1] - hints.globals.time_scale.range[0])
folder.add(self.make_animation(hints))
folder.add(self.make_track_folder(hints))
folder.add(self.make_shadow_folder(hints))
folder.add(self.make_altitude_marks_folder(hints))
folder.add(self.make_graphs_folder(hints))
return folder
Loading…
Cancel
Save