From b28730baa3035a3890da6752348223425fa5fe17 Mon Sep 17 00:00:00 2001 From: Serge Noiraud Date: Fri, 27 Apr 2012 13:45:29 +0000 Subject: [PATCH] Geography : geofamclose : Have these two families been able to meet? svn: r19389 --- src/plugins/lib/maps/lifewaylayer.py | 2 +- src/plugins/view/geofamclose.py | 687 +++++++++++++++++++++++++++ src/plugins/view/geography.gpr.py | 18 + 3 files changed, 706 insertions(+), 1 deletion(-) create mode 100644 src/plugins/view/geofamclose.py diff --git a/src/plugins/lib/maps/lifewaylayer.py b/src/plugins/lib/maps/lifewaylayer.py index d47af5bb6..ac1d5c82a 100644 --- a/src/plugins/lib/maps/lifewaylayer.py +++ b/src/plugins/lib/maps/lifewaylayer.py @@ -122,7 +122,7 @@ class LifeWayLayer(gobject.GObject, osmgpsmap.GpsMapLayer): ctx.set_source_rgba(color.red / 65535, color.green / 65535, color.blue / 65535, - 0.2) + 0.1) # transparency ggc = drawable.new_gc() rds = float(lifeway[2]) for point in lifeway[0]: diff --git a/src/plugins/view/geofamclose.py b/src/plugins/view/geofamclose.py new file mode 100644 index 000000000..f6e47c35a --- /dev/null +++ b/src/plugins/view/geofamclose.py @@ -0,0 +1,687 @@ +# -*- python -*- +# -*- coding: utf-8 -*- +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id:$ + +""" +Geography for two families +""" +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- +from gen.ggettext import gettext as _ +import operator +import gtk +from math import * + +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +import logging +_LOG = logging.getLogger("GeoGraphy.geofamilyclose") + +#------------------------------------------------------------------------- +# +# Gramps Modules +# +#------------------------------------------------------------------------- +import gen.lib +import config +import DateHandler +from gen.display.name import displayer as _nd +from PlaceUtils import conv_lat_lon +from gui.views.navigationview import NavigationView +import Bookmarks +from maps import constants +from maps.geography import GeoGraphyView +from gui.selectors import SelectorFactory + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- + +_UI_DEF = '''\ + + + + + + + + + + + + + + + + + + + + + + + + + + + +''' + +#------------------------------------------------------------------------- +# +# GeoView +# +#------------------------------------------------------------------------- +class GeoFamClose(GeoGraphyView): + """ + The view used to render family's 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.map_service', constants.OPENSTREETMAP), + + # specific to geoclose : + + ('geography.color1', 'red'), + ('geography.color2', 'green'), + ('geography.maximum_meeting_zone', 5), + + ) + + def __init__(self, pdata, dbstate, uistate, nav_group=0): + GeoGraphyView.__init__(self, _("Have these two families been able to meet?"), + pdata, dbstate, uistate, + dbstate.db.get_bookmarks(), + Bookmarks.FamilyBookmarks, + nav_group) + self.dbstate = dbstate + self.uistate = uistate + self.place_list = [] + self.all_place_list = [] + self.place_without_coordinates = [] + self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0 + self.minyear = 9999 + self.maxyear = 0 + self.reffamily = None + self.nbplaces = 0 + self.nbmarkers = 0 + self.sort = [] + self.tracks = [] + self.additional_uis.append(self.additional_ui()) + self.ref_family = None + self.skip_list = [] + self.track = [] + self.place_list_active = [] + self.place_list_ref = [] + self.cal = config.get('preferences.calendar-format-report') + + def get_title(self): + """ + Used to set the titlebar in the configuration window. + """ + return _('GeoFamClose') + + 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-family' + + def get_viewtype_stock(self): + """Type of view in category + """ + return 'geo-show-family' + + 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 'Family' + + def get_bookmarks(self): + """ + Return the bookmark object + """ + return self.dbstate.db.get_bookmarks() + + def goto_handle(self, handle=None): + """ + Rebuild the tree with the given family handle as reference. + """ + self.place_list_active = [] + self.place_list_ref = [] + self.all_place_list = [] + self.sort = [] + self.place_without_coordinates = [] + self.remove_all_gps() + self.remove_all_markers() + self.lifeway_layer.clear_ways() + if self.reffamily: + color = self._config.get('geography.color1') + self._createmap(self.reffamily, color, self.place_list_ref, True) + active = self.get_active() + f1 = None + if active: + f1 = self.dbstate.db.get_family_from_handle(active) + self.change_active(active) + color = self._config.get('geography.color2') + self._createmap(f1, color, self.place_list_active, False) + if f1 is not None: + self._possible_family_meeting(self.reffamily, f1) + self.uistate.modify_statusbar(self.dbstate) + + def define_actions(self): + """ + Define action for the reference family button. + """ + NavigationView.define_actions(self) + + self.ref_family = gtk.ActionGroup(self.title + '/Selection') + self.ref_family.add_actions([ + ('reffamily', 'gramps-family', _('_reffamily'), None , + _("Select the family which is the reference for life ways"), + self.selectFamily), + ]) + self._add_action_group(self.ref_family) + + def selectFamily(self, obj): + """ + Open a selection box to choose the ref family. + """ + self.track = [] + self.skip_list = [] + selectFamily = SelectorFactory('Family') + sel = selectFamily(self.dbstate, self.uistate) + self.reffamily = sel.run() + self.goto_handle(None) + + 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() + family = self.dbstate.db.get_family_from_handle(active) + self.lifeway_layer.clear_ways() + self.goto_handle(handle=family) + + def draw(self, menu, marks, color, reference): + """ + Create all moves for the people's event. + """ + points = [] + mark = None + for mark in marks: + startlat = float(mark[3]) + startlon = float(mark[4]) + not_stored = True + for idx in range(0, len(points)): + if points[idx][0] == startlat and points[idx][1] == startlon: + not_stored = False + if not_stored: + points.append((startlat, startlon)) + self.lifeway_layer.add_way(points, color) + if reference: + self.lifeway_layer.add_way_ref(points, color, + float(self._config.get("geography.maximum_meeting_zone")) / 10) + if mark: + self.lifeway_layer.add_text(points, mark[1]) + return False + + def _place_list_for_person(self, person): + """ + get place list for one person + """ + list = [] + for event in self.sort: + if ( event[1] == _nd.display(person)): + list.append(event) + return list + + def possible_meeting(self, ref_person, person): + """ + Try to see if two persons can be to the same place during their life. + If yes, show a marker with the dates for each person. + """ + self.place_list_ref = self._place_list_for_person(ref_person) + self.place_list_active = self._place_list_for_person(person) + radius = float(self._config.get("geography.maximum_meeting_zone")/10.0) + for ref in self.place_list_ref: + for act in self.place_list_active: + if (hypot(float(act[3])-float(ref[3]), + float(act[4])-float(ref[4])) <= radius) == True: + # we are in the meeting zone + self.add_marker(None, None, act[3], act[4], act[7], True) + self.all_place_list.append(act) + self.add_marker(None, None, ref[3], ref[4], ref[7], True) + self.all_place_list.append(ref) + + def _expose_persone_to_family(self, ref_person, family): + """ + try to search one or more meeting zone for all persons of one family + with one reference person + """ + dbstate = self.dbstate + try: + person = dbstate.db.get_person_from_handle(family.get_father_handle()) + except: + return + if person is None: # family without father ? + person = dbstate.db.get_person_from_handle(family.get_mother_handle()) + if person is not None: + family_list = person.get_family_handle_list() + if len(family_list) > 0: + fhandle = family_list[0] # first is primary + fam = dbstate.db.get_family_from_handle(fhandle) + handle = fam.get_father_handle() + father = dbstate.db.get_person_from_handle(handle) + if father: + self.possible_meeting(father, ref_person) + handle = fam.get_mother_handle() + mother = dbstate.db.get_person_from_handle(handle) + if mother: + self.possible_meeting(mother, ref_person) + child_ref_list = fam.get_child_ref_list() + if child_ref_list: + for child_ref in child_ref_list: + child = dbstate.db.get_person_from_handle(child_ref.ref) + if child: + self.possible_meeting(child, ref_person) + else: + self.possible_meeting(person, ref_person) + + + def _possible_family_meeting(self, reference, family): + """ + try to expose each person of the reference family to the second family + """ + dbstate = self.dbstate + try: + person = dbstate.db.get_person_from_handle(reference.get_father_handle()) + except: + return + if person is None: # family without father ? + person = dbstate.db.get_person_from_handle(reference.get_mother_handle()) + if person is None: + person = dbstate.db.get_person_from_handle(self.uistate.get_active('Person')) + if person is not None: + family_list = person.get_family_handle_list() + if len(family_list) > 0: + fhandle = family_list[0] # first is primary + fam = dbstate.db.get_family_from_handle(fhandle) + handle = fam.get_father_handle() + father = dbstate.db.get_person_from_handle(handle) + if father: + self._expose_persone_to_family(father, family) + handle = fam.get_mother_handle() + mother = dbstate.db.get_person_from_handle(handle) + if mother: + self._expose_persone_to_family(mother, family) + child_ref_list = fam.get_child_ref_list() + if child_ref_list: + for child_ref in child_ref_list: + child = dbstate.db.get_person_from_handle(child_ref.ref) + if child: + self._expose_persone_to_family(child, family) + else: + self._expose_persone_to_family(person, family) + + + def _createmap_for_one_person(self, person, color, place_list, reference): + """ + Create all markers for each people's event in the database which has + a lat/lon. + """ + self.place_list = [] + dbstate = self.dbstate + if person is not None: + # For each event, if we have a place, set a marker. + for event_ref in person.get_event_ref_list(): + if not event_ref: + continue + event = dbstate.db.get_event_from_handle(event_ref.ref) + role = event_ref.get_role() + try: + date = event.get_date_object().to_calendar(self.cal) + except: + continue + eyear = str("%04d" % date.get_year()) + \ + str("%02d" % date.get_month()) + \ + str("%02d" % date.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 = place.get_title() + evt = gen.lib.EventType(event.get_type()) + descr1 = _("%(eventtype)s : %(name)s") % { + 'eventtype': evt, + 'name': _nd.display(person)} + # 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() + descr1 = " - " + 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) + handle = fam.get_father_handle() + father = dbstate.db.get_person_from_handle(handle) + if father: + descr1 = "%s - " % _nd.display(father) + handle = fam.get_mother_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) + 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 = place.get_title() + evt = gen.lib.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()) + 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) + + sort1 = sorted(self.place_list, key=operator.itemgetter(6)) + self.draw(None, sort1, color, reference) + # merge with the last results + merge_list = [] + for the_list in self.sort, sort1 : merge_list += the_list + self.sort = sorted(merge_list, key=operator.itemgetter(6)) + + def _createmap_for_one_family(self, family, color, place_list, reference): + """ + Create all markers for one family : all event's places with a lat/lon. + """ + dbstate = self.dbstate + try: + person = dbstate.db.get_person_from_handle(family.get_father_handle()) + except: + return + family_id = family.gramps_id + if person is None: # family without father ? + person = dbstate.db.get_person_from_handle(family.get_mother_handle()) + if person is None: + person = dbstate.db.get_person_from_handle(self.uistate.get_active('Person')) + if person is not None: + family_list = person.get_family_handle_list() + if len(family_list) > 0: + fhandle = family_list[0] # first is primary + fam = dbstate.db.get_family_from_handle(fhandle) + handle = fam.get_father_handle() + father = dbstate.db.get_person_from_handle(handle) + if father: + comment = _("Father : %s : %s") % ( father.gramps_id, + _nd.display(father) ) + self._createmap_for_one_person(father, color, place_list, reference) + handle = fam.get_mother_handle() + mother = dbstate.db.get_person_from_handle(handle) + if mother: + comment = _("Mother : %s : %s") % ( mother.gramps_id, + _nd.display(mother) ) + self._createmap_for_one_person(mother, color, place_list, reference) + index = 0 + child_ref_list = fam.get_child_ref_list() + if child_ref_list: + for child_ref in child_ref_list: + child = dbstate.db.get_person_from_handle(child_ref.ref) + if child: + index += 1 + comment = _("Child : %(id)s - %(index)d " + ": %(name)s") % { + 'id' : child.gramps_id, + 'index' : index, + 'name' : _nd.display(child) + } + self._createmap_for_one_person(child, color, place_list, reference) + else: + comment = _("Person : %(id)s %(name)s has no family.") % { + 'id' : person.gramps_id , + 'name' : _nd.display(person) + } + self._createmap_for_one_person(person, color, place_list, reference) + + def _createmap(self, family_x, color, place_list, reference): + """ + Create all markers for each family's person in the database which has + a lat/lon. + """ + dbstate = self.dbstate + self.cal = config.get('preferences.calendar-format-report') + self.place_list = place_list + self.place_without_coordinates = [] + self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0 + #self.minyear = 9999 + #self.maxyear = 0 + latitude = "" + longitude = "" + self.place_list = [] + self.place_without_coordinates = [] + self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0 + #family = self.dbstate.db.get_family_from_handle(family_x) + family = family_x + if family is None: + person = self.dbstate.db.get_family_from_handle(self.uistate.get_active('Person')) + if not person: + return + 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: + self._createmap_for_one_family(family, color, place_list, reference) + else: + self._createmap_for_one_family(family, color, place_list, reference) + #self._create_markers() + + def bubble_message(self, event, lat, lon, marks): + """ + Create the menu for the selected marker + """ + menu = gtk.Menu() + menu.set_title("family") + events = [] + message = "" + oldplace = "" + prevmark = None + for mark in marks: + for plce in self.all_place_list: + if (plce[3] == mark[3] and plce[4] == mark[4]): + if plce[10] in events: + continue + else: + events.append(plce[10]) + + if plce[0] != oldplace: + message = "%s :" % plce[0] + self.add_place_bubble_message(event, lat, lon, + marks, menu, + message, plce) + oldplace = plce[0] + message = "" + evt = self.dbstate.db.get_event_from_gramps_id(plce[10]) + # format the date as described in preferences. + date = DateHandler.displayer.display(evt.get_date_object()) + if date == "": + date = _("Unknown") + if ( plce[11] == gen.lib.EventRoleType.PRIMARY ): + message = "(%s) %s : %s" % ( date, plce[2], plce[1] ) + elif ( plce[11] == gen.lib.EventRoleType.FAMILY ): + (father_name, mother_name) = self._get_father_and_mother_name(evt) + message = "(%s) %s : %s - %s" % (date, plce[7], + father_name, + mother_name ) + else: + descr = evt.get_description() + if descr == "": + descr = _('No description') + message = "(%s) %s => %s" % ( date, plce[11], descr) + prevmark = plce + add_item = gtk.MenuItem(message) + add_item.show() + menu.append(add_item) + itemoption = gtk.Menu() + itemoption.set_title(message) + itemoption.show() + add_item.set_submenu(itemoption) + modify = gtk.MenuItem(_("Edit Event")) + modify.show() + modify.connect("activate", self.edit_event, + event, lat, lon, prevmark) + itemoption.append(modify) + center = gtk.MenuItem(_("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, 0, event.time) + return 0 + + 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(_("Choose the reference family")) + add_item.connect("activate", self.selectFamily) + 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. + """ + table = gtk.Table(2, 2) + table.set_border_width(12) + table.set_col_spacings(6) + table.set_row_spacings(6) + configdialog.add_text(table, + _('The meeting zone probability radius.\n' + 'The colored zone is approximative.\n' + 'The value 9 means about 42 miles or 67 kms.\n' + 'The value 1 means about 4.6 miles or 7.5 kms.\n' + 'The value is in tenth of degree.'), + 1) + self.config_meeting_slider = configdialog.add_slider(table, + "", + 2, 'geography.maximum_meeting_zone', + (1, 9)) + return _('The selection parameters'), table + + def config_connect(self): + """ + used to monitor changes in the ini file + """ + self._config.connect('geography.maximum_meeting_zone', + self.cb_update_meeting_radius) + + def cb_update_meeting_radius(self, client, cnxn_id, entry, data): + """ + Called when the radius change + """ + self.goto_handle(handle=None) + diff --git a/src/plugins/view/geography.gpr.py b/src/plugins/view/geography.gpr.py index 9bda41a36..cc3cec7ff 100644 --- a/src/plugins/view/geography.gpr.py +++ b/src/plugins/view/geography.gpr.py @@ -130,6 +130,24 @@ if OSMGPSMAP: category = ("Geography", _("Geography")), viewclass = 'GeoClose', order = END, + stock_icon = 'gramps-relation', + ) + + register(VIEW, + id = 'closefmap', + name = _("Have these two families been able to meet?"), + description = _("A view showing the places visited by " + "all family's members during their life: " + "have these two people been able to meet?"), + version = '1.0.1', + gramps_target_version = MODULE_VERSION, + status = STABLE, + fname = 'geofamclose.py', + authors = [u"Serge Noiraud"], + authors_email = [""], + category = ("Geography", _("Geography")), + viewclass = 'GeoFamClose', + #order = END, stock_icon = 'geo-show-family', )