class TrackFile(Coordinates): """Parent class for all types of GPS track files. Subclasses must implement element_start and element_end, and call them in the base class. """ def __init__(self, filename, root, watch): self.filename = filename self.progress = get_obj('progressbar') self.clock = clock() self.append = None self.tracks = {} self.polygons = set() self.parser = XMLSimpleParser(root, watch) self.parser.parse(filename, self.element_start, self.element_end) empty_trackfile_label.hide() points.update(self.tracks) keys = self.tracks.keys() self.alpha = min(keys) self.omega = max(keys) self.latitude = self.tracks[self.alpha].lat self.longitude = self.tracks[self.alpha].lon # TODO find some kind of parent widget that can group these together # to make it easier to get them and insert them into places. builder = Builder('trackfile') self.colorpicker = builder.get_object('colorpicker') self.trash = builder.get_object('unload') self.label = builder.get_object('trackfile_label') self.label.set_text(basename(filename)) self.colorpicker.set_title(basename(filename)) self.colorpicker.connect('color-set', track_color_changed, self.polygons) self.trash.connect('clicked', self.destroy) get_obj('trackfiles_view').attach_next_to( builder.get_object('trackfile_settings'), None, BOTTOM, 1, 1) self.gst = GSettings('trackfile', basename(filename)) if self.gst.get_string('start-timezone') is '': # Then this is the first time this file has been loaded # and we should honor the user-selected global default # track color instead of using the schema-defined default self.gst.set_value('track-color', gst.get_value('track-color')) self.gst.set_string('start-timezone', self.lookup_geoname()) self.gst.bind_with_convert('track-color', self.colorpicker, 'color', lambda x: Gdk.Color(*x), lambda x: (x.red, x.green, x.blue)) self.colorpicker.emit('color-set') def element_start(self, name, attributes): """Placeholder for a method that gets overridden in subclasses.""" return False def element_end(self, name, state): """Occasionally redraw the screen so the user can see what's happening.""" if clock() - self.clock > .2: self.progress.pulse() while Gtk.events_pending(): Gtk.main_iteration() self.clock = clock() def destroy(self, button=None): """Die a horrible death.""" for polygon in self.polygons: map_view.remove_layer(polygon) for timestamp in self.tracks: del points[timestamp] self.polygons.clear() for widget in (self.label, self.colorpicker, self.trash): widget.destroy() del known_trackfiles[self.filename] if not known_trackfiles: empty_trackfile_label.show()
class TrackFile(): """Parent class for all types of GPS track files. Subclasses must implement at least element_end. """ range = [] parse = XMLSimpleParser instances = set() @staticmethod def update_range(): """Ensure that TrackFile.range contains the correct info.""" while TrackFile.range: TrackFile.range.pop() if not TrackFile.instances: Widgets.empty_trackfile_list.show() else: Widgets.empty_trackfile_list.hide() TrackFile.range.extend([min(points), max(points)]) @staticmethod def get_bounding_box(): """Determine the smallest box that contains all loaded polygons.""" bounds = Champlain.BoundingBox.new() for trackfile in TrackFile.instances: for polygon in trackfile.polygons: bounds.compose(polygon.get_bounding_box()) return bounds @staticmethod def query_all_timezones(): """Try to determine the most likely timezone the user is in. First we check all TrackFiles for the timezone at their starting point, and if they are all identical, we report it. If they do not match, then the user must travel a lot, and then we simply have no idea what timezone is likely to be the one that their camera is set to. """ zones = set() for trackfile in TrackFile.instances: zones.add(trackfile.start.geotimezone) return None if len(zones) != 1 else zones.pop() @staticmethod def clear_all(*ignore): """Forget all GPX data, start over with a clean slate.""" for trackfile in list(TrackFile.instances): trackfile.destroy() points.clear() @staticmethod def load_from_file(uri): """Determine the correct subclass to instantiate. Also time everything and report how long it took. Raises IOError if the file extension is unknown, or no track points were found. """ start_time = clock() try: gpx = globals()[uri[-3:].upper() + 'File'](uri) except KeyError: raise IOError Widgets.status_message(_('%d points loaded in %.2fs.') % (len(gpx.tracks), clock() - start_time), True) if len(gpx.tracks) < 2: return TrackFile.instances.add(gpx) MapView.emit('realize') MapView.set_zoom_level(MapView.get_max_zoom_level()) MapView.ensure_visible(TrackFile.get_bounding_box(), False) TrackFile.update_range() Camera.set_all_found_timezone(gpx.start.geotimezone) def __init__(self, filename, root, watch): self.watchlist = watch self.filename = filename self.progress = Widgets.progressbar self.polygons = set() self.widgets = Builder('trackfile') self.append = None self.tracks = {} self.clock = clock() self.gst = GSettings('trackfile', basename(filename)) if self.gst.get_string('start-timezone') is '': # Then this is the first time this file has been loaded # and we should honor the user-selected global default # track color instead of using the schema-defined default self.gst.set_value('track-color', Gst.get_value('track-color')) self.gst.bind_with_convert( 'track-color', self.widgets.colorpicker, 'color', lambda x: Gdk.Color(*x), lambda x: (x.red, x.green, x.blue)) self.widgets.trackfile_label.set_text(basename(filename)) self.widgets.unload.connect('clicked', self.destroy) self.widgets.colorpicker.set_title(basename(filename)) self.widgets.colorpicker.connect('color-set', track_color_changed, self.polygons) Widgets.trackfile_unloads_group.add_widget(self.widgets.unload) Widgets.trackfile_colors_group.add_widget(self.widgets.colorpicker) Widgets.trackfiles_group.add_widget(self.widgets.trackfile_label) self.parse(filename, root, watch, self.element_start, self.element_end) if not self.tracks: raise IOError('No points found') points.update(self.tracks) keys = self.tracks.keys() self.alpha = min(keys) self.omega = max(keys) self.start = Coordinates(latitude = self.tracks[self.alpha].lat, longitude = self.tracks[self.alpha].lon) self.gst.set_string('start-timezone', self.start.lookup_geodata()) Widgets.trackfiles_view.add(self.widgets.trackfile_settings) def element_start(self, name, attributes=None): """Determine when new tracks start and create a new Polygon.""" if name == self.watchlist[0]: polygon = Polygon() self.polygons.add(polygon) self.append = polygon.append_point self.widgets.colorpicker.emit('color-set') return False return True def element_end(self, name=None, state=None): """Occasionally redraw the screen so the user can see activity.""" if clock() - self.clock > .2: self.progress.pulse() while Gtk.events_pending(): Gtk.main_iteration() self.clock = clock() def destroy(self, button=None): """Die a horrible death.""" for polygon in self.polygons: MapView.remove_layer(polygon) self.polygons.clear() self.widgets.trackfile_settings.destroy() del self.cache[self.filename] TrackFile.instances.discard(self) points.clear() for trackfile in TrackFile.instances: points.update(trackfile.tracks) TrackFile.update_range()