# -*- python -*- # -*- coding: utf-8 -*- # # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2011-2016 Serge Noiraud # # 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 2 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, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # """ Geography for one person """ #------------------------------------------------------------------------- # # Python modules # #------------------------------------------------------------------------- import operator from gi.repository import Gdk KEY_TAB = Gdk.KEY_Tab from gi.repository import Gtk from gi.repository import GLib #------------------------------------------------------------------------- # # set up logging # #------------------------------------------------------------------------- import logging _LOG = logging.getLogger("GeoGraphy.geoperson") #------------------------------------------------------------------------- # # Gramps Modules # #------------------------------------------------------------------------- from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext from gramps.gen.lib import EventRoleType, EventType from gramps.gen.config import config from gramps.gen.datehandler import displayer from gramps.gen.display.name import displayer as _nd from gramps.gen.display.place import displayer as _pd from gramps.gen.utils.place import conv_lat_lon from gramps.gui.views.bookmarks import PersonBookmarks from gramps.plugins.lib.maps import constants from gramps.plugins.lib.maps.geography import GeoGraphyView #------------------------------------------------------------------------- # # Constants # #------------------------------------------------------------------------- _UI_DEF = '''\ ''' # pylint: disable=no-member # pylint: disable=maybe-no-member # pylint: disable=unused-variable # pylint: disable=unused-argument #------------------------------------------------------------------------- # # GeoView # #------------------------------------------------------------------------- class GeoPerson(GeoGraphyView): """ The view used to render person map. """ CONFIGSETTINGS = ( ('geography.path', constants.GEOGRAPHY_PATH), ('geography.zoom', 10), ('geography.zoom_when_center', 12), ('geography.show_cross', True), ('geography.lock', False), ('geography.center-lat', 0.0), ('geography.center-lon', 0.0), ('geography.use-keypad', True), #('geography.gps_mode', GPS_DISABLED), #('geography.gps_update_rate', float(1.0)), #('geography.max_gps_zoom', 16), #('geography.gps_increment', GPS_INCREMENT), ('geography.map_service', constants.OPENSTREETMAP), ('geography.max_places', 5000), # specific to geoperson : ('geography.steps', 20), ('geography.maximum_lon_lat', 30), ('geography.speed', 100), ) def __init__(self, pdata, dbstate, uistate, nav_group=0): GeoGraphyView.__init__(self, _("Person places map"), pdata, dbstate, uistate, PersonBookmarks, nav_group) self.dbstate = dbstate self.uistate = uistate self.place_list = [] self.place_without_coordinates = [] self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0 self.minyear = 9999 self.maxyear = 0 self.nbplaces = 0 self.nbmarkers = 0 self.sort = [] self.additional_uis.append(self.additional_ui()) self.no_show_places_in_status_bar = False self.already_started = False self.large_move = False self.cal = None def get_title(self): """ Used to set the titlebar in the configuration window. """ return _('GeoPerson') def get_stock(self): """ Returns the name of the stock icon to use for the display. This assumes that this icon has already been registered as a stock icon. """ return 'geo-show-person' def get_viewtype_stock(self): """Type of view in category """ return 'geo-show-person' def additional_ui(self): """ Specifies the UIManager XML code that defines the menus and buttons associated with the interface. """ return _UI_DEF def navigation_type(self): """ Indicates the navigation type. Navigation type can be the string name of any of the primary objects. """ return 'Person' def goto_handle(self, handle=None): """ Rebuild the tree with the given person handle as the root. """ active = self.get_active() #if handle: # self._createmap(handle) #elif active: # p1 = self.dbstate.db.get_person_from_handle(active) # self._createmap(p1) self._createmap() self.uistate.modify_statusbar(self.dbstate) def build_tree(self): """ This is called by the parent class when the view becomes visible. Since all handling of visibility is now in rebuild_trees, see that for more information. """ active = self.get_active() #self._createmap(active) self._createmap() self.uistate.modify_statusbar(self.dbstate) def animate(self, menu, marks, index, stepyear): """ Create all movements for the people's event. Yes, you can see the person moving. """ if len(marks) == 0: self.already_started = False return False i = int(index) next_i = i + 1 if next_i == len(marks): self.already_started = False return False startlat = float(marks[i][3]) startlon = float(marks[i][4]) heading = 1 if index == 0 and stepyear == 0: self.remove_all_gps() self.large_move = False self.osm.gps_add(startlat, startlon, heading) endlat = float(marks[next_i][3]) endlon = float(marks[next_i][4]) max_lon_lat = float(self._config.get("geography.maximum_lon_lat")) / 10 if stepyear < 9000: if ((abs(float(endlat) - float(startlat)) > max_lon_lat) or (abs(float(endlon) - float(startlon)) > max_lon_lat)): self.large_move = True stepyear = 9000 else: self.large_move = False # year format = YYYYMMDD ( for sort ) startyear = str(marks[i][6])[0:4] endyear = str(marks[next_i][6])[0:4] endmov = str(marks[len(marks)-1][6])[0:4] years = int(endyear) - int(startyear) if years < 1: years = 1 if stepyear > 8999: latstep = (endlat - startlat) / self._config.get("geography.steps") lonstep = (endlon - startlon) / self._config.get("geography.steps") startlat += (latstep * (stepyear - 8999)) startlon += (lonstep * (stepyear - 8999)) else: latstep = (endlat - startlat) / years lonstep = (endlon - startlon) / years stepyear = 1 if stepyear < 1 else stepyear startlat += (latstep * stepyear) startlon += (lonstep * stepyear) self.osm.gps_add(startlat, startlon, heading) stepyear += 1 difflat = round((startlat - endlat) if startlat > endlat else \ (endlat - startlat), 8) difflon = round((startlon - endlon) if startlon > endlon else \ (endlon - startlon), 8) if difflat == 0.0 and difflon == 0.0: i += 1 self.large_move = False stepyear = 1 # if geography.speed = 100 => 100ms => 1s per 10 years. # For a 100 years person, it takes 10 secondes. # if large_move, one step is the difflat or difflon / geography.steps # in this case, stepyear is >= 9000 # large move means longitude or latitude differences greater than # geography.maximum_lon_lat degrees. GLib.timeout_add(int(self._config.get("geography.speed")), self.animate, menu, marks, i, stepyear) return False def _createmap(self): """ Create all markers for each people's event in the database which has a lat/lon. """ dbstate = self.dbstate self.cal = config.get('preferences.calendar-format-report') self.place_list = [] self.place_without_coordinates = [] self.places_found = [] self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0 self.minyear = 9999 self.maxyear = 0 latitude = "" longitude = "" self.nbplaces = 0 self.nbmarkers = 0 self.message_layer.clear_messages() self.kml_layer.clear() person_handle = self.uistate.get_active('Person') person = None if person_handle: person = dbstate.db.get_person_from_handle(person_handle) if person is not None: # For each event, if we have a place, set a marker. self.load_kml_files(person) self.message_layer.add_message( _("Person places for %s") % _nd.display(person)) for event_ref in person.get_event_ref_list(): if not event_ref: continue event = dbstate.db.get_event_from_handle(event_ref.ref) self.load_kml_files(event) role = event_ref.get_role() eyear = str( "%04d" % event.get_date_object().to_calendar(self.cal).get_year()) + \ str("%02d" % event.get_date_object().to_calendar(self.cal).get_month()) + \ str("%02d" % event.get_date_object().to_calendar(self.cal).get_day()) place_handle = event.get_place_handle() if place_handle: place = dbstate.db.get_place_from_handle(place_handle) if place: longitude = place.get_longitude() latitude = place.get_latitude() latitude, longitude = conv_lat_lon(latitude, longitude, "D.D8") descr = _pd.display(dbstate.db, place) evt = EventType(event.get_type()) descr1 = _("%(eventtype)s : %(name)s") % { 'eventtype': evt, 'name': _nd.display(person)} self.load_kml_files(place) # place.get_longitude and place.get_latitude return # one string. We have coordinates when the two values # contains non null string. if longitude and latitude: self._append_to_places_list(descr, evt, _nd.display(person), latitude, longitude, descr1, eyear, event.get_type(), person.gramps_id, place.gramps_id, event.gramps_id, role ) else: self._append_to_places_without_coord( place.gramps_id, descr) family_list = person.get_family_handle_list() for family_hdl in family_list: family = self.dbstate.db.get_family_from_handle(family_hdl) if family is not None: fhandle = family_list[0] # first is primary fam = dbstate.db.get_family_from_handle(fhandle) father = mother = None handle = fam.get_father_handle() if handle: father = dbstate.db.get_person_from_handle(handle) descr1 = " - " if father: descr1 = "%s - " % _nd.display(father) handle = fam.get_mother_handle() if handle: mother = dbstate.db.get_person_from_handle(handle) if mother: descr1 = "%s%s" % (descr1, _nd.display(mother)) for event_ref in family.get_event_ref_list(): if event_ref: event = dbstate.db.get_event_from_handle( event_ref.ref) self.load_kml_files(event) role = event_ref.get_role() if event.get_place_handle(): place_handle = event.get_place_handle() if place_handle: place = dbstate.db.get_place_from_handle( place_handle) if place: longitude = place.get_longitude() latitude = place.get_latitude() (latitude, longitude) = conv_lat_lon(latitude, longitude, "D.D8") descr = _pd.display(dbstate.db, place) evt = EventType(event.get_type()) eyear = str( "%04d" % event.get_date_object().to_calendar(self.cal).get_year()) + \ str("%02d" % event.get_date_object().to_calendar(self.cal).get_month()) + \ str("%02d" % event.get_date_object().to_calendar(self.cal).get_day()) self.load_kml_files(place) if longitude and latitude: self._append_to_places_list(descr, evt, _nd.display(person), latitude, longitude, descr1, eyear, event.get_type(), person.gramps_id, place.gramps_id, event.gramps_id, role ) else: self._append_to_places_without_coord(place.gramps_id, descr) self.sort = sorted(self.place_list, key=operator.itemgetter(6) ) self._create_markers() def bubble_message(self, event, lat, lon, marks): self.menu = Gtk.Menu() menu = self.menu menu.set_title("person") message = "" oldplace = "" prevmark = None for mark in marks: if oldplace != "": add_item = Gtk.MenuItem(label=message) add_item.show() menu.append(add_item) self.itemoption = Gtk.Menu() itemoption = self.itemoption itemoption.set_title(message) itemoption.show() message = "" add_item.set_submenu(itemoption) modify = Gtk.MenuItem(label=_("Edit Event")) modify.show() modify.connect("activate", self.edit_event, event, lat, lon, prevmark) itemoption.append(modify) center = Gtk.MenuItem(label=_("Center on this place")) center.show() center.connect("activate", self.center_here, event, lat, lon, prevmark) itemoption.append(center) if mark[0] != oldplace: if message != "": add_item = Gtk.MenuItem() add_item.show() menu.append(add_item) self.itemoption = Gtk.Menu() itemoption = self.itemoption itemoption.set_title(message) itemoption.show() message = "" add_item.set_submenu(itemoption) modify = Gtk.MenuItem(label=_("Edit Event")) modify.show() modify.connect("activate", self.edit_event, event, lat, lon, mark) itemoption.append(modify) center = Gtk.MenuItem(label=_("Center on this place")) center.show() center.connect("activate", self.center_here, event, lat, lon, mark) itemoption.append(center) message = "%s :" % mark[0] self.add_place_bubble_message(event, lat, lon, marks, menu, message, mark) oldplace = mark[0] message = "" evt = self.dbstate.db.get_event_from_gramps_id(mark[10]) # format the date as described in preferences. date = displayer.display(evt.get_date_object()) if date == "": date = _("Unknown") if mark[11] == EventRoleType.PRIMARY: message = "(%s) %s : %s" % (date, mark[2], mark[1]) elif mark[11] == EventRoleType.FAMILY: (father_name, mother_name) = self._get_father_and_mother_name(evt) message = "(%s) %s : %s - %s" % (date, mark[7], father_name, mother_name) else: descr = evt.get_description() if descr == "": descr = _('No description') message = "(%s) %s => %s" % (date, mark[11], descr) prevmark = mark add_item = Gtk.MenuItem(label=message) add_item.show() menu.append(add_item) self.itemoption = Gtk.Menu() itemoption = self.itemoption itemoption.set_title(message) itemoption.show() add_item.set_submenu(itemoption) modify = Gtk.MenuItem(label=_("Edit Event")) modify.show() modify.connect("activate", self.edit_event, event, lat, lon, prevmark) itemoption.append(modify) center = Gtk.MenuItem(label=_("Center on this place")) center.show() center.connect("activate", self.center_here, event, lat, lon, prevmark) itemoption.append(center) menu.show() menu.popup(None, None, None, None, event.button, event.time) return 1 def add_specific_menu(self, menu, event, lat, lon): """ Add specific entry to the navigation menu. """ add_item = Gtk.MenuItem() add_item.show() menu.append(add_item) add_item = Gtk.MenuItem(label=_("Animate")) add_item.connect("activate", self.animate, self.sort, 0, 0) add_item.show() menu.append(add_item) return def get_default_gramplets(self): """ Define the default gramplets for the sidebar and bottombar. """ return (("Person Filter",), ()) def specific_options(self, configdialog): """ Add specific entry to the preference menu. Must be done in the associated view. """ grid = Gtk.Grid() grid.set_border_width(12) grid.set_column_spacing(6) grid.set_row_spacing(6) configdialog.add_text(grid, _('Animation speed in milliseconds (big value means slower)'), 1, line_wrap=False) configdialog.add_slider(grid, "", 2, 'geography.speed', (100, 1000)) configdialog.add_text( grid, _('How many steps between two markers when we are on large move ?'), 3, line_wrap=False) configdialog.add_slider(grid, "", 4, 'geography.steps', (10, 100)) configdialog.add_text(grid, _('The minimum latitude/longitude to select large move.\n' 'The value is in tenth of degree.'), 5, line_wrap=False) configdialog.add_slider(grid, "", 6, 'geography.maximum_lon_lat', (5, 50)) return _('The animation parameters'), grid