gramps/gramps/plugins/view/geoperson.py

543 lines
22 KiB
Python

# -*- 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Geography for one person
"""
#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
import os
import sys
import operator
from gi.repository import Gdk
KEY_TAB = Gdk.KEY_Tab
import socket
from gi.repository import Gtk
from gi.repository import GObject
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.pageview import PageView
from gramps.gui.editors import EditPlace
from gramps.gui.selectors.selectplace import SelectPlace
from gramps.gui.filters.sidebar import PersonSidebarFilter
from gramps.gui.views.navigationview import NavigationView
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 = '''\
<ui>
<menubar name="MenuBar">
<menu action="GoMenu">
<placeholder name="CommonGo">
<menuitem action="Back"/>
<menuitem action="Forward"/>
<separator/>
<menuitem action="HomePerson"/>
<separator/>
</placeholder>
</menu>
<menu action="EditMenu">
<placeholder name="CommonEdit">
<menuitem action="PrintView"/>
</placeholder>
</menu>
<menu action="BookMenu">
<placeholder name="AddEditBook">
<menuitem action="AddBook"/>
<menuitem action="EditBook"/>
</placeholder>
</menu>
</menubar>
<toolbar name="ToolBar">
<placeholder name="CommonNavigation">
<toolitem action="Back"/>
<toolitem action="Forward"/>
<toolitem action="HomePerson"/>
</placeholder>
<placeholder name="CommonEdit">
<toolitem action="PrintView"/>
</placeholder>
</toolbar>
</ui>
'''
#-------------------------------------------------------------------------
#
# 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
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.
"""
self.places_found = []
active = self.get_active()
if handle:
self._createmap(handle)
elif active:
p1 = self.dbstate.db.get_person_from_handle(active)
self._createmap(p1)
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)
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)
ni = i + 1
if ni == 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[ni][3])
endlon = float(marks[ni][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[ni][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,obj):
"""
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.minlat = self.maxlat = self.minlon = self.maxlon = 0.0
self.minyear = 9999
self.maxyear = 0
latitude = ""
longitude = ""
self.message_layer.clear_messages()
person_handle = self.uistate.get_active('Person')
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.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)
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)}
# 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)
handle = fam.get_father_handle()
father = dbstate.db.get_person_from_handle(handle)
descr1 = " - "
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 = _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())
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.
"""
table = Gtk.Table(n_rows=2, n_columns=2)
table.set_border_width(12)
table.set_col_spacings(6)
table.set_row_spacings(6)
configdialog.add_text(table,
_('Animation speed in milliseconds (big value means slower)'),
1, line_wrap=False)
configdialog.add_slider(table,
"",
2, 'geography.speed',
(100, 1000))
configdialog.add_text(table,
_('How many steps between two markers when we are on large move ?'),
3, line_wrap=False)
configdialog.add_slider(table,
"",
4, 'geography.steps',
(10, 100))
configdialog.add_text(table,
_('The minimum latitude/longitude to select large move.\n'
'The value is in tenth of degree.'),
5, line_wrap=False)
configdialog.add_slider(table,
"",
6, 'geography.maximum_lon_lat',
(5, 50))
return _('The animation parameters'), table