Exemplo n.º 1
0
 def __init__(self, filename):
     """Raises IOError for invalid file types.
     
     This MUST be the case in order to avoid the @memoize cache getting
     filled up with invalid Photograph instances.
     """
     Coordinates.__init__(self)
     self.thumb = fetch_thumbnail(filename)
     self.filename = filename
     
     self.connect('notify::geoname', self.update_liststore_summary)
     self.connect('notify::positioned', Widgets.button_sensitivity)
Exemplo n.º 2
0
    def __init__(self):
        """Start the map at the previous location, and connect signals."""
        perform_zoom = lambda button, zoom: zoom()
        back_button = get_obj('back_button')
        zoom_in_button = get_obj('zoom_in_button')
        zoom_out_button = get_obj('zoom_out_button')
        zoom_out_button.connect('clicked', perform_zoom, map_view.zoom_out)
        zoom_in_button.connect('clicked', perform_zoom, map_view.zoom_in)
        back_button.connect('clicked', go_back, map_view)

        for key in ['latitude', 'longitude', 'zoom-level']:
            gst.bind(key, map_view, key)

        accel = Gtk.AccelGroup()
        window = get_obj('main')
        window.add_accel_group(accel)
        for key in ['Left', 'Right', 'Up', 'Down']:
            accel.connect(Gdk.keyval_from_name(key),
                          Gdk.ModifierType.MOD1_MASK, 0, move_by_arrow_keys)

        map_view.connect('notify::zoom-level', zoom_button_sensitivity,
                         zoom_in_button.set_sensitive,
                         zoom_out_button.set_sensitive)
        map_view.connect('realize', remember_location)
        map_view.connect('animation-completed', set_window_title,
                         window.set_title, Coordinates())
        map_view.emit('animation-completed')
Exemplo n.º 3
0
 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)
Exemplo n.º 4
0
 def __str__(self):
     """Long summary of photo metadata with Pango markup."""
     summary = '<span %s>%s</span>\n<span %s>%s</span>' % (
         'size="larger"', basename(self.filename),
         'style="italic" size="smaller"', Coordinates.__str__(self))
     return '<b>%s</b>' % summary if self in modified else summary
Exemplo n.º 5
0
    def test_gps_math(self):
        """Test coordinate conversion functions."""
        rats_to_fracs = lambda rats: [Fraction(rat.to_float()) for rat in rats]

        # Really important that this method is bulletproof
        self.assertFalse(valid_coords(None, None))
        self.assertFalse(valid_coords('', ''))
        self.assertFalse(valid_coords(True, True))
        self.assertFalse(valid_coords(False, False))
        self.assertFalse(valid_coords(45, 270))
        self.assertFalse(valid_coords(100, 50))
        self.assertFalse(valid_coords([], 50))
        self.assertFalse(valid_coords(45, {'grunt':42}))
        self.assertFalse(valid_coords(self, 50))
        self.assertFalse(valid_coords(45, valid_coords))
        self.assertFalse(valid_coords("ya", "dun goofed"))

        # St. John's broke the math. I'm not sure why.
        # Seriously. Revert commit 362dd6eb and watch this explode.
        stjohns = Coordinates()
        stjohns.latitude = 47.56494
        stjohns.longitude = -52.70931
        stjohns.lookup_geoname()
        self.assertEqual(stjohns.city, "St. John's")

        # Pick 100 random coordinates on the globe, convert them from decimal
        # to sexagesimal and then back, and ensure that they are always equal.
        for _ in range(100):
            # Oh, and test altitudes too
            altitude = round(random_coord(1000), 6)
            fraction = float_to_rational(altitude)
            self.assertAlmostEqual(
                abs(altitude),
                fraction.numerator / fraction.denominator,
                3
            )

            decimal_lat = round(random_coord(80),  6)
            decimal_lon = round(random_coord(180), 6)

            self.assertTrue(valid_coords(decimal_lat, decimal_lon))

            dms_lat = decimal_to_dms(decimal_lat)
            dms_lon = decimal_to_dms(decimal_lon)

            self.assertEqual(len(dms_lat), 3)
            self.assertEqual(
                dms_lat[0].numerator,
                floor(abs(decimal_lat))
            )

            self.assertEqual(len(dms_lon), 3)
            self.assertEqual(
                dms_lon[0].numerator,
                floor(abs(decimal_lon))
            )

            self.assertAlmostEqual(
                decimal_lat,
                dms_to_decimal(*rats_to_fracs(dms_lat) + ['N' if decimal_lat >= 0 else 'S']),
                10 # equal to 10 places
            )
            self.assertAlmostEqual(
                decimal_lon,
                dms_to_decimal(*rats_to_fracs(dms_lon) + ['E' if decimal_lon >= 0 else 'W']),
                10 # equal to 10 places
            )
Exemplo n.º 6
0
 def test_gps_math(self):
     """Test coordinate conversion functions."""
     rats_to_fracs = lambda rats: [Fraction(rat.to_float()) for rat in rats]
     
     # Really important that this method is bulletproof
     self.assertFalse(valid_coords(None, None))
     self.assertFalse(valid_coords('', ''))
     self.assertFalse(valid_coords(True, True))
     self.assertFalse(valid_coords(False, False))
     self.assertFalse(valid_coords(45, 270))
     self.assertFalse(valid_coords(100, 50))
     self.assertFalse(valid_coords([], 50))
     self.assertFalse(valid_coords(45, {'grunt':42}))
     self.assertFalse(valid_coords(self, 50))
     self.assertFalse(valid_coords(45, valid_coords))
     self.assertFalse(valid_coords("ya", "dun goofed"))
     
     # St. John's broke the math. I'm not sure why.
     # Seriously. Revert commit 362dd6eb and watch this explode.
     stjohns = Coordinates()
     stjohns.latitude = 47.56494
     stjohns.longitude = -52.70931
     stjohns.lookup_geoname()
     self.assertEqual(stjohns.city, "St. John's")
     
     # Pick 100 random coordinates on the globe, convert them from decimal
     # to sexagesimal and then back, and ensure that they are always equal.
     for i in range(100):
         # Oh, and test altitudes too
         altitude = round(random_coord(1000), 6)
         fraction = float_to_rational(altitude)
         self.assertAlmostEqual(
             abs(altitude),
             fraction.numerator / fraction.denominator,
             3
         )
         
         decimal_lat = round(random_coord(80),  6)
         decimal_lon = round(random_coord(180), 6)
         
         self.assertTrue(valid_coords(decimal_lat, decimal_lon))
         
         dms_lat = decimal_to_dms(decimal_lat)
         dms_lon = decimal_to_dms(decimal_lon)
         
         self.assertEqual(len(dms_lat), 3)
         self.assertEqual(
             dms_lat[0].numerator,
             floor(abs(decimal_lat))
         )
         
         self.assertEqual(len(dms_lon), 3)
         self.assertEqual(
             dms_lon[0].numerator,
             floor(abs(decimal_lon))
         )
         
         self.assertAlmostEqual(
             decimal_lat,
             dms_to_decimal(*rats_to_fracs(dms_lat) + ['N' if decimal_lat >= 0 else 'S']),
             10 # equal to 10 places
         )
         self.assertAlmostEqual(
             decimal_lon,
             dms_to_decimal(*rats_to_fracs(dms_lon) + ['E' if decimal_lon >= 0 else 'W']),
             10 # equal to 10 places
         )
Exemplo n.º 7
0
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()