1241 lines
45 KiB
Python
1241 lines
45 KiB
Python
# -*- 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.
|
|
#
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Python modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
|
_ = glocale.translation.sgettext
|
|
import os
|
|
import re
|
|
import time
|
|
from gi.repository import GLib
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# GTK/Gnome modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
from gi.repository import Gtk
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Gramps Modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
from gramps.gen.lib import EventType, Place, PlaceRef, PlaceName
|
|
from gramps.gen.display.name import displayer as _nd
|
|
from gramps.gen.display.place import displayer as _pd
|
|
from gramps.gui.views.navigationview import NavigationView
|
|
from gramps.gen.utils.libformatting import FormattingHelper
|
|
from gramps.gen.errors import WindowActiveError
|
|
from gramps.gen.const import HOME_DIR
|
|
from gramps.gen.config import config
|
|
from gramps.gui.editors import EditPlace, EditEvent, EditFamily, EditPerson
|
|
from gramps.gui.selectors.selectplace import SelectPlace
|
|
|
|
import gi
|
|
gi.require_version('OsmGpsMap', '1.0')
|
|
from gi.repository import OsmGpsMap as osmgpsmap
|
|
from . import constants
|
|
from .osmgps import OsmGps
|
|
from .selectionlayer import SelectionLayer
|
|
from .placeselection import PlaceSelection
|
|
from .cairoprint import CairoPrintSave
|
|
from .libkml import Kml
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# Set up logging
|
|
#
|
|
#------------------------------------------------------------------------
|
|
import logging
|
|
_LOG = logging.getLogger("maps.geography")
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Constants
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
GEOGRAPHY_PATH = os.path.join(HOME_DIR, "maps")
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Functions and variables
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
PLACE_REGEXP = re.compile('<span background="green">(.*)</span>')
|
|
PLACE_STRING = '<span background="green">%s</span>'
|
|
|
|
# pylint: disable=unused-argument
|
|
# pylint: disable=unused-variable
|
|
# pylint: disable=no-member
|
|
# pylint: disable=maybe-no-member
|
|
|
|
def _get_sign(value):
|
|
"""
|
|
return 1 if we have a negative number, 0 in other case
|
|
"""
|
|
if value < 0.0:
|
|
return 1
|
|
else:
|
|
return 0
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# GeoGraphyView
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
class GeoGraphyView(OsmGps, NavigationView):
|
|
"""
|
|
View for pedigree tree.
|
|
Displays the ancestors of a selected individual.
|
|
"""
|
|
#settings in the config file
|
|
CONFIGSETTINGS = (
|
|
('geography.path', 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),
|
|
('geography.max_places', 5000),
|
|
('geography.use-keypad', True),
|
|
)
|
|
|
|
def __init__(self, title, pdata, dbstate, uistate,
|
|
bm_type, nav_group):
|
|
NavigationView.__init__(self, title, pdata, dbstate, uistate,
|
|
bm_type, nav_group)
|
|
|
|
OsmGps.__init__(self, uistate)
|
|
self.dbstate = dbstate
|
|
self.dbstate.connect('database-changed', self.change_db)
|
|
self.default_text = "Enter location here!"
|
|
self.centerlon = config.get("geography.center-lon")
|
|
self.centerlat = config.get("geography.center-lat")
|
|
self.zoom = config.get("geography.zoom")
|
|
self.lock = config.get("geography.lock")
|
|
if config.get('geography.path') == "":
|
|
config.set('geography.path', GEOGRAPHY_PATH)
|
|
|
|
self.format_helper = FormattingHelper(self.dbstate)
|
|
self.centerlat = self.centerlon = 0.0
|
|
self.cross_map = None
|
|
self.current_map = None
|
|
self.without = 0
|
|
self.place_list = []
|
|
self.places_found = []
|
|
self.select_fct = None
|
|
self.geo_mainmap = None
|
|
theme = Gtk.IconTheme.get_default()
|
|
self.geo_mainmap = theme.load_surface('gramps-geo-mainmap', 48, 1,
|
|
None, 0)
|
|
self.geo_altmap = theme.load_surface('gramps-geo-altmap', 48, 1,
|
|
None, 0)
|
|
if (config.get('geography.map_service') in
|
|
(constants.OPENSTREETMAP,
|
|
constants.MAPS_FOR_FREE,
|
|
constants.OPENCYCLEMAP,
|
|
constants.OSM_PUBLIC_TRANSPORT,
|
|
)):
|
|
default_image = self.geo_mainmap
|
|
else:
|
|
default_image = self.geo_altmap
|
|
self.geo_othermap = {}
|
|
for ident in (EventType.BIRTH,
|
|
EventType.DEATH,
|
|
EventType.MARRIAGE):
|
|
icon = constants.ICONS.get(int(ident))
|
|
self.geo_othermap[ident] = theme.load_surface(icon, 48, 1, None, 0)
|
|
self.maxyear = 0
|
|
self.minyear = 9999
|
|
self.maxlat = 0.0
|
|
self.minlat = 0.0
|
|
self.maxlon = 0.0
|
|
self.minlon = 0.0
|
|
self.longt = 0.0
|
|
self.latit = 0.0
|
|
self.itemoption = None
|
|
self.menu = None
|
|
self.mark = None
|
|
self.path_entry = None
|
|
self.changemap = None
|
|
self.clearmap = None
|
|
self.nbplaces = 0
|
|
|
|
def add_bookmark(self, menu):
|
|
"""
|
|
Add the place to the bookmark
|
|
"""
|
|
mlist = self.selected_handles()
|
|
if mlist:
|
|
self.bookmarks.add(mlist[0])
|
|
else:
|
|
from gramps.gui.dialog import WarningDialog
|
|
WarningDialog( # parent-OK
|
|
_("Could Not Set a Bookmark"),
|
|
_("A bookmark could not be set because "
|
|
"no one was selected."),
|
|
parent=self.uistate.window)
|
|
|
|
def add_bookmark_from_popup(self, menu, handle):
|
|
"""
|
|
Add the place to the bookmark from the popup menu
|
|
"""
|
|
if handle:
|
|
self.uistate.set_active(handle, self.navigation_type())
|
|
self.bookmarks.add(handle)
|
|
self.bookmarks.redraw()
|
|
else:
|
|
from gramps.gui.dialog import WarningDialog
|
|
WarningDialog( # parent-OK
|
|
_("Could Not Set a Bookmark"),
|
|
_("A bookmark could not be set because "
|
|
"no one was selected."),
|
|
parent=self.uistate.window)
|
|
|
|
def change_page(self):
|
|
"""
|
|
Called when the page changes.
|
|
"""
|
|
NavigationView.change_page(self)
|
|
self.uistate.clear_filter_results()
|
|
self.end_selection = None
|
|
self.osm.grab_focus()
|
|
self.set_crosshair(config.get("geography.show_cross"))
|
|
|
|
def do_size_request(self, requisition):
|
|
"""
|
|
Overridden method to handle size request events.
|
|
"""
|
|
requisition.width = 400
|
|
requisition.height = 300
|
|
|
|
def do_get_preferred_width(self):
|
|
""" GTK3 uses width for height sizing model. This method will
|
|
override the virtual method
|
|
"""
|
|
req = Gtk.Requisition()
|
|
self.do_size_request(req)
|
|
return req.width, req.width
|
|
|
|
def do_get_preferred_height(self):
|
|
""" GTK3 uses width for height sizing model. This method will
|
|
override the virtual method
|
|
"""
|
|
req = Gtk.Requisition()
|
|
self.do_size_request(req)
|
|
return req.height, req.height
|
|
|
|
def on_delete(self):
|
|
"""
|
|
Save all modified environment
|
|
"""
|
|
NavigationView.on_delete(self)
|
|
self._config.save()
|
|
|
|
def change_db(self, dbse):
|
|
"""
|
|
Callback associated with DbState. Whenever the database
|
|
changes, this task is called. In this case, we rebuild the
|
|
columns, and connect signals to the connected database. Tree
|
|
is no need to store the database, since we will get the value
|
|
from self.state.db
|
|
"""
|
|
if self.active:
|
|
self.bookmarks.redraw()
|
|
self.build_tree()
|
|
self.osm.grab_focus()
|
|
self.set_crosshair(config.get("geography.show_cross"))
|
|
|
|
def can_configure(self):
|
|
"""
|
|
See :class:`~gui.views.pageview.PageView
|
|
:return: bool
|
|
"""
|
|
return True
|
|
|
|
def define_actions(self):
|
|
"""
|
|
Required define_actions function for PageView. Builds the action
|
|
group information required.
|
|
As this function is overriden in some plugins, we need to call
|
|
another method.
|
|
"""
|
|
NavigationView.define_actions(self)
|
|
self.define_print_actions()
|
|
|
|
def define_print_actions(self):
|
|
"""
|
|
Associate the print button to the PrintView action.
|
|
"""
|
|
self._add_action('PrintView', 'document-print', _("_Print..."),
|
|
accel="<PRIMARY>P",
|
|
tip=_("Print or save the Map"),
|
|
callback=self.printview)
|
|
|
|
def config_connect(self):
|
|
"""
|
|
Overwriten from :class:`~gui.views.pageview.PageView method
|
|
This method will be called after the ini file is initialized,
|
|
use it to monitor changes in the ini file
|
|
"""
|
|
self._config.connect("geography.path",
|
|
self.set_path)
|
|
self._config.connect("geography.zoom_when_center",
|
|
self.set_zoom_when_center)
|
|
|
|
def set_path(self, client, cnxn_id, entry, data):
|
|
"""
|
|
All geography views must have the same path for maps
|
|
"""
|
|
config.set("geography.path", entry)
|
|
|
|
def set_zoom_when_center(self, client, cnxn_id, entry, data):
|
|
"""
|
|
All geography views must have the same zoom_when_center for maps
|
|
"""
|
|
config.set("geography.zoom_when_center", int(entry))
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Map Menu
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
def build_nav_menu(self, obj, event, lat, lon):
|
|
"""
|
|
Builds the menu for actions on the map.
|
|
"""
|
|
self.menu = Gtk.Menu()
|
|
menu = self.menu
|
|
menu.set_title(_('Map Menu'))
|
|
|
|
if config.get("geography.show_cross"):
|
|
title = _('Remove cross hair')
|
|
else:
|
|
title = _('Add cross hair')
|
|
add_item = Gtk.MenuItem(label=title)
|
|
add_item.connect("activate", self.config_crosshair, event, lat, lon)
|
|
add_item.show()
|
|
menu.append(add_item)
|
|
|
|
if config.get("geography.lock"):
|
|
title = _('Unlock zoom and position')
|
|
else:
|
|
title = _('Lock zoom and position')
|
|
add_item = Gtk.MenuItem(label=title)
|
|
add_item.connect("activate", self.config_zoom_and_position,
|
|
event, lat, lon)
|
|
add_item.show()
|
|
menu.append(add_item)
|
|
|
|
add_item = Gtk.MenuItem(label=_("Add place"))
|
|
add_item.connect("activate", self.add_place, event, lat, lon)
|
|
add_item.show()
|
|
menu.append(add_item)
|
|
|
|
add_item = Gtk.MenuItem(label=_("Link place"))
|
|
add_item.connect("activate", self.link_place, event, lat, lon)
|
|
add_item.show()
|
|
menu.append(add_item)
|
|
|
|
add_item = Gtk.MenuItem(label=_("Add place from kml"))
|
|
add_item.connect("activate", self.add_place_from_kml, event, lat, lon)
|
|
add_item.show()
|
|
menu.append(add_item)
|
|
|
|
add_item = Gtk.MenuItem(label=_("Center here"))
|
|
add_item.connect("activate", self.set_center, event, lat, lon)
|
|
add_item.show()
|
|
menu.append(add_item)
|
|
|
|
# Add specific module menu
|
|
self.add_specific_menu(menu, event, lat, lon)
|
|
# Add a separator line
|
|
add_item = Gtk.MenuItem()
|
|
add_item.show()
|
|
menu.append(add_item)
|
|
|
|
map_name = constants.MAP_TITLE[config.get("geography.map_service")]
|
|
title = _("Replace '%(map)s' by =>") % {
|
|
'map' : map_name
|
|
}
|
|
add_item = Gtk.MenuItem(label=title)
|
|
add_item.show()
|
|
menu.append(add_item)
|
|
|
|
self.changemap = Gtk.Menu()
|
|
changemap = self.changemap
|
|
changemap.set_title(title)
|
|
changemap.show()
|
|
add_item.set_submenu(changemap)
|
|
# show in the map menu all available providers
|
|
for my_map in constants.MAP_TYPE:
|
|
changemapitem = Gtk.MenuItem(label=constants.MAP_TITLE[my_map])
|
|
changemapitem.show()
|
|
changemapitem.connect("activate", self.change_map, my_map)
|
|
changemap.append(changemapitem)
|
|
|
|
clear_text = _("Clear the '%(map)s' tiles cache.") % {
|
|
'map' : map_name
|
|
}
|
|
self.clearmap = Gtk.MenuItem(label=clear_text)
|
|
clearmap = self.clearmap
|
|
clearmap.connect("activate", self.clear_map,
|
|
constants.TILES_PATH[config.get(
|
|
"geography.map_service")])
|
|
|
|
clearmap.show()
|
|
menu.append(clearmap)
|
|
menu.show()
|
|
menu.popup(None, None, None,
|
|
None, event.button, event.time)
|
|
return 1
|
|
|
|
|
|
def clear_map(self, menu, the_map):
|
|
"""
|
|
We need to clean the tiles cache for the current map
|
|
"""
|
|
import shutil
|
|
|
|
path = "%s%c%s" % (config.get('geography.path'), os.sep, the_map)
|
|
shutil.rmtree(path)
|
|
|
|
def add_specific_menu(self, menu, event, lat, lon):
|
|
"""
|
|
Add specific entry to the navigation menu.
|
|
Must be done in the associated menu.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def set_center(self, menu, event, lat, lon):
|
|
"""
|
|
Center the map at the new position then save it.
|
|
"""
|
|
self.osm.set_center_and_zoom(lat, lon,
|
|
config.get("geography.zoom_when_center"))
|
|
self.save_center(lat, lon)
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Markers management
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
def is_there_a_marker_here(self, event, lat, lon):
|
|
"""
|
|
Is there a marker at this position ?
|
|
"""
|
|
found = False
|
|
mark_selected = []
|
|
self.uistate.set_busy_cursor(True)
|
|
for mark in self.sort:
|
|
# as we are not precise with our hand, reduce the precision
|
|
# depending on the zoom.
|
|
precision = {
|
|
1 : '%3.0f', 2 : '%3.1f', 3 : '%3.1f', 4 : '%3.1f',
|
|
5 : '%3.2f', 6 : '%3.2f', 7 : '%3.2f', 8 : '%3.3f',
|
|
9 : '%3.3f', 10 : '%3.3f', 11 : '%3.3f', 12 : '%3.3f',
|
|
13 : '%3.3f', 14 : '%3.4f', 15 : '%3.4f', 16 : '%3.4f',
|
|
17 : '%3.4f', 18 : '%3.4f'
|
|
}.get(config.get("geography.zoom"), '%3.1f')
|
|
shift = {
|
|
1 : 5.0, 2 : 5.0, 3 : 3.0,
|
|
4 : 1.0, 5 : 0.5, 6 : 0.3, 7 : 0.15,
|
|
8 : 0.06, 9 : 0.03, 10 : 0.015,
|
|
11 : 0.005, 12 : 0.003, 13 : 0.001,
|
|
14 : 0.0005, 15 : 0.0003, 16 : 0.0001,
|
|
17 : 0.0001, 18 : 0.0001
|
|
}.get(config.get("geography.zoom"), 5.0)
|
|
latp = precision % lat
|
|
lonp = precision % lon
|
|
mlatp = precision % float(mark[3])
|
|
mlonp = precision % float(mark[4])
|
|
latok = lonok = False
|
|
_LOG.debug(" compare latitude : %s with %s (precision = %s)"
|
|
" place='%s'", float(mark[3]), lat, precision, mark[0])
|
|
_LOG.debug("compare longitude : %s with %s (precision = %s)"
|
|
" zoom=%d", float(mark[4]), lon, precision,
|
|
config.get("geography.zoom"))
|
|
if (float(mlatp) >= (float(latp) - shift)) and \
|
|
(float(mlatp) <= (float(latp) + shift)):
|
|
latok = True
|
|
if (float(mlonp) >= (float(lonp) - shift)) and \
|
|
(float(mlonp) <= (float(lonp) + shift)):
|
|
lonok = True
|
|
if latok and lonok:
|
|
mark_selected.append(mark)
|
|
found = True
|
|
if found:
|
|
self.bubble_message(event, lat, lon, mark_selected)
|
|
self.uistate.set_busy_cursor(False)
|
|
|
|
def bubble_message(self, event, lat, lon, mark):
|
|
"""
|
|
Display the bubble message. depends on the view.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def add_selection_layer(self):
|
|
"""
|
|
add the selection layer
|
|
"""
|
|
selection_layer = SelectionLayer()
|
|
self.osm.layer_add(selection_layer)
|
|
return selection_layer
|
|
|
|
def remove_layer(self, layer):
|
|
"""
|
|
Remove the specified layer
|
|
"""
|
|
self.osm.remove_layer(layer)
|
|
|
|
def add_marker(self, menu, event, lat, lon, event_type, differtype, count):
|
|
"""
|
|
Add a new marker
|
|
"""
|
|
mapservice = config.get('geography.map_service')
|
|
if (mapservice in (constants.OPENSTREETMAP,
|
|
constants.OPENSTREETMAP_RENDERER)):
|
|
default_image = self.geo_mainmap
|
|
else:
|
|
default_image = self.geo_altmap
|
|
value = default_image
|
|
if event_type is not None:
|
|
value = self.geo_othermap.get(int(event_type), default_image)
|
|
if differtype: # in case multiple evts
|
|
value = default_image # we use default icon.
|
|
self.marker_layer.add_marker((float(lat), float(lon)), value, count)
|
|
|
|
def remove_all_gps(self):
|
|
"""
|
|
Remove all gps points on the map
|
|
"""
|
|
self.osm.gps_clear()
|
|
|
|
def remove_all_tracks(self):
|
|
"""
|
|
Remove all tracks on the map
|
|
"""
|
|
self.osm.track_remove_all()
|
|
|
|
def remove_all_markers(self):
|
|
"""
|
|
Remove all markers on the map
|
|
"""
|
|
self.marker_layer.clear_markers()
|
|
|
|
def _present_in_places_list(self, index, string):
|
|
"""
|
|
Search a string in place_list depending index
|
|
"""
|
|
found = any(p[index] == string for p in self.place_list)
|
|
return found
|
|
|
|
def _append_to_places_list(self, place, evttype, name, lat,
|
|
longit, descr, year, icontype,
|
|
gramps_id, place_id, event_id, family_id
|
|
):
|
|
"""
|
|
Create a list of places with coordinates.
|
|
"""
|
|
found = any(p[0] == place for p in self.places_found)
|
|
if not found and (self.nbplaces <
|
|
self._config.get("geography.max_places")):
|
|
# We only show the first "geography.max_places".
|
|
# over 3000 or 4000 places, the geography become unusable.
|
|
# In this case, filter the places ...
|
|
self.nbplaces += 1
|
|
self.places_found.append([place, lat, longit])
|
|
self.place_list.append([place, name, evttype, lat,
|
|
longit, descr, year, icontype,
|
|
gramps_id, place_id, event_id, family_id
|
|
])
|
|
self.nbmarkers += 1
|
|
tfa = float(lat)
|
|
tfb = float(longit)
|
|
if year is not None:
|
|
tfc = int(year)
|
|
if tfc != 0:
|
|
if tfc < self.minyear:
|
|
self.minyear = tfc
|
|
if tfc > self.maxyear:
|
|
self.maxyear = tfc
|
|
tfa += 0.00000001 if tfa >= 0 else -0.00000001
|
|
tfb += 0.00000001 if tfb >= 0 else -0.00000001
|
|
if self.minlat == 0.0 or tfa < self.minlat:
|
|
self.minlat = tfa
|
|
if self.maxlat == 0.0 or tfa > self.maxlat:
|
|
self.maxlat = tfa
|
|
if self.minlon == 0.0 or tfb < self.minlon:
|
|
self.minlon = tfb
|
|
if self.maxlon == 0.0 or tfb > self.maxlon:
|
|
self.maxlon = tfb
|
|
|
|
def _append_to_places_without_coord(self, gid, place):
|
|
"""
|
|
Create a list of places without coordinates.
|
|
"""
|
|
if not [gid, place] in self.place_without_coordinates:
|
|
self.place_without_coordinates.append([gid, place])
|
|
self.without += 1
|
|
|
|
def _create_markers(self):
|
|
"""
|
|
Create all markers for the specified person.
|
|
"""
|
|
self.remove_all_markers()
|
|
self.remove_all_gps()
|
|
self.remove_all_tracks()
|
|
if (self.current_map is not None and
|
|
self.current_map != config.get("geography.map_service")):
|
|
self.change_map(self.osm, config.get("geography.map_service"))
|
|
last = ""
|
|
current = ""
|
|
differtype = False
|
|
#savetype = None
|
|
lat = 0.0
|
|
lon = 0.0
|
|
icon = None
|
|
count = 0
|
|
self.uistate.set_busy_cursor(True)
|
|
_LOG.debug("%s", time.strftime("start create_marker : "
|
|
"%a %d %b %Y %H:%M:%S", time.gmtime()))
|
|
for mark in self.sort:
|
|
current = ([mark[3], mark[4]])
|
|
if last == "":
|
|
last = current
|
|
lat = mark[3]
|
|
lon = mark[4]
|
|
icon = mark[7]
|
|
differtype = False
|
|
count = 1
|
|
continue
|
|
if last != current:
|
|
self.add_marker(None, None, lat, lon, icon, differtype, count)
|
|
differtype = False
|
|
count = 1
|
|
last = current
|
|
lat = mark[3]
|
|
lon = mark[4]
|
|
icon = mark[7]
|
|
else: # This marker already exists. add info.
|
|
count += 1
|
|
if icon != mark[7]:
|
|
differtype = True
|
|
if lat != 0.0 and lon != 0.0:
|
|
self.add_marker(None, None, lat, lon, icon, differtype, count)
|
|
self._set_center_and_zoom()
|
|
_LOG.debug("%s", time.strftime(" stop create_marker : "
|
|
"%a %d %b %Y %H:%M:%S", time.gmtime()))
|
|
self.uistate.set_busy_cursor(False)
|
|
|
|
def _visible_marker(self, lat, lon):
|
|
"""
|
|
Is this marker in the visible area ?
|
|
"""
|
|
bbox = self.osm.get_bbox()
|
|
s_lon = lon + 10.0
|
|
s_lat = lat + 10.0
|
|
pt1 = bbox[0]
|
|
s_bbox_lat1 = pt1.rlat + 10.0
|
|
s_bbox_lon1 = pt1.rlon + 10.0
|
|
pt2 = bbox[1]
|
|
s_bbox_lat2 = pt2.rlat + 10.0
|
|
s_bbox_lon2 = pt2.rlon + 10.0
|
|
result = ((s_bbox_lat1 > s_lat > s_bbox_lat2) and
|
|
(s_bbox_lon1 < s_lon < s_bbox_lon2))
|
|
return result
|
|
|
|
def _autozoom_in(self, lvl, p1lat, p1lon, p2lat, p2lon):
|
|
"""
|
|
We zoom in until at least one marker missing.
|
|
"""
|
|
if ((self._visible_marker(p1lat, p1lon)
|
|
and self._visible_marker(p2lat, p2lon))
|
|
and lvl < 18):
|
|
lvl += 1
|
|
self.osm.set_zoom(lvl)
|
|
GLib.timeout_add(int(50), self._autozoom_in, lvl,
|
|
p1lat, p1lon, p2lat, p2lon)
|
|
else:
|
|
GLib.timeout_add(int(50), self._autozoom_out, lvl,
|
|
p1lat, p1lon, p2lat, p2lon)
|
|
|
|
def _autozoom_out(self, lvl, p1lat, p1lon, p2lat, p2lon):
|
|
"""
|
|
We zoom out until all markers visible.
|
|
"""
|
|
if (not (self._visible_marker(p1lat, p1lon)
|
|
and self._visible_marker(p2lat, p2lon))
|
|
and lvl > 1):
|
|
lvl -= 1
|
|
self.osm.set_zoom(lvl)
|
|
GLib.timeout_add(int(50), self._autozoom_out, lvl,
|
|
p1lat, p1lon, p2lat, p2lon)
|
|
else:
|
|
layer = self.get_selection_layer()
|
|
if layer:
|
|
self.osm.layer_remove(layer)
|
|
|
|
def _autozoom(self):
|
|
"""
|
|
Try to put all markers on the map. we start at current zoom.
|
|
If all markers are present, continue to zoom.
|
|
If some markers are missing : return to the zoom - 1
|
|
We must use function called by timeout to force map updates.
|
|
"""
|
|
level_start = self.osm.props.zoom
|
|
p1lat, p1lon = self.begin_selection.get_degrees()
|
|
p2lat, p2lon = self.end_selection.get_degrees()
|
|
lat = p1lat + (p2lat - p1lat) / 2
|
|
lon = p1lon + (p2lon - p1lon) / 2
|
|
# We center the map on the center of the region
|
|
self.osm.set_center(lat, lon)
|
|
self.save_center(lat, lon)
|
|
p1lat = self.begin_selection.rlat
|
|
p1lon = self.begin_selection.rlon
|
|
p2lat = self.end_selection.rlat
|
|
p2lon = self.end_selection.rlon
|
|
# We zoom in until at least one marker missing.
|
|
GLib.timeout_add(int(50), self._autozoom_in, level_start,
|
|
p1lat, p1lon, p2lat, p2lon)
|
|
|
|
def _set_center_and_zoom(self):
|
|
"""
|
|
Calculate the zoom.
|
|
The best should be an auto zoom to have all markers on the screen.
|
|
need some works here.
|
|
we start at zoom 1 until zoom y ( for this a preference )
|
|
If all markers are present, continue to zoom.
|
|
If some markers are missing : return to the zoom - 1
|
|
The following is too complex. In some case, all markers are not present.
|
|
"""
|
|
# Select the center of the map and the zoom
|
|
signminlon = _get_sign(self.minlon)
|
|
signminlat = _get_sign(self.minlat)
|
|
signmaxlon = _get_sign(self.maxlon)
|
|
signmaxlat = _get_sign(self.maxlat)
|
|
current = osmgpsmap.MapPoint.new_degrees(self.minlat, self.minlon)
|
|
self.end_selection = current
|
|
current = osmgpsmap.MapPoint.new_degrees(self.maxlat, self.maxlon)
|
|
self.begin_selection = current
|
|
if signminlon == signmaxlon:
|
|
maxlong = abs(abs(self.minlon) - abs(self.maxlon))
|
|
else:
|
|
maxlong = abs(abs(self.minlon) + abs(self.maxlon))
|
|
if signminlat == signmaxlat:
|
|
maxlat = abs(abs(self.minlat) - abs(self.maxlat))
|
|
else:
|
|
maxlat = abs(abs(self.minlat) + abs(self.maxlat))
|
|
latit = longt = 0.0
|
|
for mark in self.sort:
|
|
if signminlat == signmaxlat:
|
|
if signminlat == 1:
|
|
latit = self.minlat+self.centerlat
|
|
else:
|
|
latit = self.maxlat-self.centerlat
|
|
elif self.maxlat > self.centerlat:
|
|
latit = self.maxlat-self.centerlat
|
|
else:
|
|
latit = self.minlat+self.centerlat
|
|
if signminlon == signmaxlon:
|
|
if signminlon == 1:
|
|
longt = self.minlon+self.centerlon
|
|
else:
|
|
longt = self.maxlon-self.centerlon
|
|
elif self.maxlon > self.centerlon:
|
|
longt = self.maxlon-self.centerlon
|
|
else:
|
|
longt = self.minlon+self.centerlon
|
|
# all maps: 0.0 for longitude and latitude means no location.
|
|
if latit == longt == 0.0:
|
|
latit = longt = 0.00000001
|
|
self.latit = latit
|
|
self.longt = longt
|
|
if config.get("geography.lock"):
|
|
self.osm.set_center_and_zoom(config.get("geography.center-lat"),
|
|
config.get("geography.center-lon"),
|
|
config.get("geography.zoom"))
|
|
else:
|
|
self._autozoom()
|
|
self.save_center(self.latit, self.longt)
|
|
config.set("geography.zoom", self.osm.props.zoom)
|
|
self.end_selection = None
|
|
|
|
def _get_father_and_mother_name(self, event):
|
|
"""
|
|
Return the father and mother name of a family event
|
|
"""
|
|
dbstate = self.dbstate
|
|
family_list = [
|
|
dbstate.db.get_family_from_handle(ref_handle)
|
|
for (ref_type, ref_handle) in
|
|
dbstate.db.find_backlink_handles(event.handle)
|
|
if ref_type == 'Family'
|
|
]
|
|
fnam = mnam = _("Unknown")
|
|
if family_list:
|
|
for family in family_list:
|
|
father = mother = None
|
|
handle = family.get_father_handle()
|
|
if handle:
|
|
father = dbstate.db.get_person_from_handle(handle)
|
|
handle = family.get_mother_handle()
|
|
if handle:
|
|
mother = dbstate.db.get_person_from_handle(handle)
|
|
fnam = _nd.display(father) if father else _("Unknown")
|
|
mnam = _nd.display(mother) if mother else _("Unknown")
|
|
return (fnam, mnam)
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# KML functionalities
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
def load_kml_files(self, obj):
|
|
"""
|
|
obj can be an event, a person or a place
|
|
"""
|
|
media_list = obj.get_media_list()
|
|
if media_list:
|
|
for media_ref in media_list:
|
|
object_handle = media_ref.get_reference_handle()
|
|
media_obj = self.dbstate.db.get_media_from_handle(object_handle)
|
|
path = media_obj.get_path()
|
|
name, extension = os.path.splitext(path)
|
|
if extension == ".kml":
|
|
if os.path.isfile(path):
|
|
self.kml_layer.add_kml(path)
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Printing functionalities
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
def printview(self, obj):
|
|
"""
|
|
Print or save the view that is currently shown
|
|
"""
|
|
if Gtk.MAJOR_VERSION == 3 and Gtk.MINOR_VERSION < 11:
|
|
from gramps.gui.dialog import WarningDialog
|
|
WarningDialog( # parent-OK
|
|
_("You can't use the print functionality"),
|
|
_("Your Gtk version is too old."),
|
|
parent=self.uistate.window)
|
|
return
|
|
|
|
req = self.osm.get_allocation()
|
|
widthpx = req.width
|
|
heightpx = req.height
|
|
prt = CairoPrintSave(widthpx, heightpx, self.osm.do_draw, self.osm)
|
|
prt.run()
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Specific functionalities
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
def center_here(self, menu, event, lat, lon, mark):
|
|
"""
|
|
Center the map at the marker position
|
|
"""
|
|
self.set_center(menu, event, float(mark[3]), float(mark[4]))
|
|
|
|
def add_place_bubble_message(self, event, lat, lon, marks,
|
|
menu, message, mark):
|
|
"""
|
|
Create the place menu of a marker
|
|
"""
|
|
add_item = Gtk.MenuItem()
|
|
add_item.show()
|
|
menu.append(add_item)
|
|
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 Place"))
|
|
modify.show()
|
|
modify.connect("activate", self.edit_place, 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)
|
|
add_item = Gtk.MenuItem()
|
|
add_item.show()
|
|
menu.append(add_item)
|
|
|
|
def edit_place(self, menu, event, lat, lon, mark):
|
|
"""
|
|
Edit the selected place at the marker position
|
|
"""
|
|
self.mark = mark
|
|
place = self.dbstate.db.get_place_from_gramps_id(self.mark[9])
|
|
parent_list = place.get_placeref_list()
|
|
if len(parent_list) > 0:
|
|
parent = parent_list[0].ref
|
|
else:
|
|
parent = None
|
|
self.select_fct = PlaceSelection(self.uistate, self.dbstate, self.osm,
|
|
self.selection_layer, self.place_list,
|
|
lat, lon, self.__edit_place, parent)
|
|
|
|
def edit_person(self, menu, event, lat, lon, mark):
|
|
"""
|
|
Edit the selected person at the marker position
|
|
"""
|
|
_LOG.debug("edit_person : %s", mark[8])
|
|
# need to add code here to edit the person.
|
|
person = self.dbstate.db.get_person_from_gramps_id(mark[8])
|
|
try:
|
|
EditPerson(self.dbstate, self.uistate, [], person)
|
|
except WindowActiveError:
|
|
pass
|
|
|
|
def edit_family(self, menu, event, lat, lon, mark):
|
|
"""
|
|
Edit the selected family at the marker position
|
|
"""
|
|
_LOG.debug("edit_family : %s", mark[11])
|
|
family = self.dbstate.db.get_family_from_gramps_id(mark[11])
|
|
try:
|
|
EditFamily(self.dbstate, self.uistate, [], family)
|
|
except WindowActiveError:
|
|
pass
|
|
|
|
def edit_event(self, menu, event, lat, lon, mark):
|
|
"""
|
|
Edit the selected event at the marker position
|
|
"""
|
|
_LOG.debug("edit_event : %s", mark[10])
|
|
event = self.dbstate.db.get_event_from_gramps_id(mark[10])
|
|
try:
|
|
EditEvent(self.dbstate, self.uistate, [], event)
|
|
except WindowActiveError:
|
|
pass
|
|
|
|
def add_place(self, menu, event, lat, lon):
|
|
"""
|
|
Add a new place using longitude and latitude of location centered
|
|
on the map
|
|
"""
|
|
self.select_fct = PlaceSelection(self.uistate, self.dbstate, self.osm,
|
|
self.selection_layer, self.place_list,
|
|
lat, lon, self.__add_place)
|
|
|
|
def add_place_from_kml(self, menu, event, lat, lon):
|
|
"""
|
|
Add new place(s) from a kml file
|
|
|
|
1 - ask for a kml file ?
|
|
2 - Read the kml file.
|
|
3 - create the place(s) with name and title found in the kml marker.
|
|
|
|
"""
|
|
# Ask for the kml file
|
|
filtering = Gtk.FileFilter()
|
|
filtering.add_pattern("*.kml")
|
|
kml = Gtk.FileChooserDialog(
|
|
_("Select a kml file used to add places"),
|
|
action=Gtk.FileChooserAction.OPEN,
|
|
parent=self.uistate.window,
|
|
buttons=(_('_Cancel'), Gtk.ResponseType.CANCEL,
|
|
_('_Apply'), Gtk.ResponseType.OK))
|
|
mpath = HOME_DIR
|
|
kml.set_current_folder(os.path.dirname(mpath))
|
|
kml.set_filter(filtering)
|
|
|
|
status = kml.run()
|
|
if status == Gtk.ResponseType.OK:
|
|
val = kml.get_filename()
|
|
if val:
|
|
kmlfile = Kml(val)
|
|
points = kmlfile.add_points()
|
|
for place in points:
|
|
(name, coords) = place
|
|
latlong = coords.pop()
|
|
(lat, lon) = latlong
|
|
place_name = PlaceName()
|
|
place_name.set_value(name)
|
|
new_place = Place()
|
|
new_place.set_name(place_name)
|
|
new_place.set_title(name)
|
|
new_place.set_latitude(str(lat))
|
|
new_place.set_longitude(str(lon))
|
|
try:
|
|
EditPlace(self.dbstate, self.uistate, [], new_place)
|
|
except WindowActiveError:
|
|
pass
|
|
kml.destroy()
|
|
|
|
def link_place(self, menu, event, lat, lon):
|
|
"""
|
|
Link an existing place using longitude and latitude of location centered
|
|
on the map
|
|
If we have a place history, we must show all places to avoid an empty
|
|
place selection in the PlaceSelection.
|
|
"""
|
|
if self.uistate.get_active('Place'):
|
|
self._createmap(None)
|
|
selector = SelectPlace(self.dbstate, self.uistate, [])
|
|
place = selector.run()
|
|
if place:
|
|
parent_list = place.get_placeref_list()
|
|
if len(parent_list) > 0:
|
|
parent = parent_list[0].ref
|
|
else:
|
|
parent = None
|
|
places_handle = self.dbstate.db.iter_place_handles()
|
|
nb_places = 0
|
|
gids = ""
|
|
place_title = _pd.display(self.dbstate.db, place)
|
|
for place_hdl in places_handle:
|
|
plce = self.dbstate.db.get_place_from_handle(place_hdl)
|
|
plce_title = _pd.display(self.dbstate.db, plce)
|
|
if plce_title == place_title:
|
|
nb_places += 1
|
|
if gids == "":
|
|
gids = plce.gramps_id
|
|
else:
|
|
gids = gids + ", " + plce.gramps_id
|
|
if nb_places > 1:
|
|
from gramps.gui.dialog import WarningDialog
|
|
WarningDialog( # parent-OK
|
|
_('You have at least two places with the same title.'),
|
|
_("The title of the places is:\n%(title)s\n"
|
|
"The following places are similar: %(gid)s\n"
|
|
"You should eiher rename the places or merge them.\n\n"
|
|
"%(bold_start)s"
|
|
"I can't proceed with your request"
|
|
"%(bold_end)s.\n") % {
|
|
'bold_start' : '<b>',
|
|
'bold_end' : '</b>',
|
|
'title': '<b>' + place_title + '</b>',
|
|
'gid': gids},
|
|
parent=self.uistate.window
|
|
)
|
|
else:
|
|
self.mark = [None, None, None, None, None, None, None,
|
|
None, None, place.gramps_id, None, None]
|
|
self.select_fct = PlaceSelection(self.uistate,
|
|
self.dbstate,
|
|
self.osm,
|
|
self.selection_layer,
|
|
self.place_list,
|
|
lat,
|
|
lon,
|
|
self.__edit_place,
|
|
parent)
|
|
|
|
def __add_place(self, parent, plat, plon):
|
|
"""
|
|
Add a new place using longitude and latitude of location centered
|
|
on the map
|
|
"""
|
|
self.select_fct.close()
|
|
new_place = Place()
|
|
new_place.set_latitude(str(plat))
|
|
new_place.set_longitude(str(plon))
|
|
if parent:
|
|
if isinstance(parent, Place):
|
|
placeref = PlaceRef()
|
|
placeref.ref = parent
|
|
new_place.add_placeref(placeref)
|
|
else:
|
|
found = None
|
|
for place in self.dbstate.db.iter_places():
|
|
found = place
|
|
if place.name.get_value() == parent:
|
|
break
|
|
placeref = PlaceRef()
|
|
placeref.ref = found.get_handle()
|
|
new_place.add_placeref(placeref)
|
|
try:
|
|
EditPlace(self.dbstate, self.uistate, [], new_place)
|
|
self.add_marker(None, None, plat, plon, None, True, 0)
|
|
except WindowActiveError:
|
|
pass
|
|
|
|
def __edit_place(self, parent, plat, plon):
|
|
"""
|
|
Edit the selected place at the marker position
|
|
"""
|
|
self.select_fct.close()
|
|
place = self.dbstate.db.get_place_from_gramps_id(self.mark[9])
|
|
place.set_latitude(str(plat))
|
|
place.set_longitude(str(plon))
|
|
try:
|
|
EditPlace(self.dbstate, self.uistate, [], place)
|
|
except WindowActiveError:
|
|
pass
|
|
|
|
def __link_place(self, parent, plat, plon):
|
|
"""
|
|
Link an existing place using longitude and latitude of location centered
|
|
on the map
|
|
"""
|
|
selector = SelectPlace(self.dbstate, self.uistate, [])
|
|
place = selector.run()
|
|
if place:
|
|
self.select_fct.close()
|
|
place.set_latitude(str(plat))
|
|
place.set_longitude(str(plon))
|
|
if parent:
|
|
placeref = PlaceRef()
|
|
placeref.ref = parent
|
|
place.add_placeref(placeref)
|
|
try:
|
|
EditPlace(self.dbstate, self.uistate, [], place)
|
|
self.add_marker(None, None, plat, plon, None, True, 0)
|
|
except WindowActiveError:
|
|
pass
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Geography preferences
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
def _get_configure_page_funcs(self):
|
|
"""
|
|
The function which is used to create the configuration window.
|
|
"""
|
|
return [self.map_options, self.specific_options]
|
|
|
|
def config_zoom_and_position(self, client, cnxn_id, entry, data):
|
|
"""
|
|
Do we need to lock the zoom and position ?
|
|
"""
|
|
if config.get("geography.lock"):
|
|
config.set("geography.lock", False)
|
|
self._set_center_and_zoom()
|
|
else:
|
|
config.set("geography.lock", True)
|
|
self.lock = config.get("geography.lock")
|
|
|
|
def config_crosshair(self, client, cnxn_id, entry, data):
|
|
"""
|
|
We asked to change the crosshair.
|
|
"""
|
|
if config.get("geography.show_cross"):
|
|
config.set("geography.show_cross", False)
|
|
else:
|
|
config.set("geography.show_cross", True)
|
|
self.set_crosshair(config.get("geography.show_cross"))
|
|
|
|
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, _('Nothing for this view.'), 0)
|
|
return _('Specific parameters'), grid
|
|
|
|
def map_options(self, configdialog):
|
|
"""
|
|
Function that builds the widget in the configuration dialog
|
|
for the map options.
|
|
"""
|
|
self._config.set('geography.path', config.get('geography.path'))
|
|
self._config.set('geography.zoom_when_center',
|
|
config.get('geography.zoom_when_center'))
|
|
self._config.set('geography.max_places',
|
|
self._config.get('geography.max_places'))
|
|
grid = Gtk.Grid()
|
|
grid.set_border_width(12)
|
|
grid.set_column_spacing(6)
|
|
grid.set_row_spacing(6)
|
|
self.path_entry = Gtk.Entry()
|
|
configdialog.add_path_box(grid,
|
|
_('Where to save the tiles for offline mode.'),
|
|
0, self.path_entry, config.get('geography.path'),
|
|
self.set_tilepath, self.select_tilepath)
|
|
configdialog.add_text(grid,
|
|
_('If you have no more space in your file system. '
|
|
'You can remove all tiles placed in the above path.\n'
|
|
'Be careful! If you have no internet, you\'ll get no map.'),
|
|
2, line_wrap=False)
|
|
configdialog.add_slider(grid,
|
|
_('Zoom used when centering'),
|
|
3, 'geography.zoom_when_center',
|
|
(2, 16))
|
|
configdialog.add_slider(grid,
|
|
_('The maximum number of places to show'),
|
|
4, 'geography.max_places',
|
|
(1000, 10000))
|
|
configdialog.add_checkbox(grid,
|
|
_('Use keypad for shortcuts :\n'
|
|
'Either we choose the + and - from the keypad if we '
|
|
'select this,\n'
|
|
'or we use the characters from the keyboard.'),
|
|
5, 'geography.use-keypad',
|
|
extra_callback=self.update_shortcuts)
|
|
return _('The map'), grid
|
|
|
|
def set_tilepath(self, *obj):
|
|
"""
|
|
Save the tile path in the config section.
|
|
"""
|
|
if self.path_entry.get_text().strip():
|
|
config.set('geography.path', self.path_entry.get_text())
|
|
else:
|
|
config.set('geography.path', GEOGRAPHY_PATH)
|
|
|
|
def select_tilepath(self, *obj):
|
|
"""
|
|
Call a file chooser selection box to select the tile path.
|
|
"""
|
|
selected_dir = Gtk.FileChooserDialog(
|
|
_("Select tile cache directory for offline mode"),
|
|
action=Gtk.FileChooserAction.SELECT_FOLDER,
|
|
parent=self.uistate.window,
|
|
buttons=(_('_Cancel'),
|
|
Gtk.ResponseType.CANCEL,
|
|
_('_Apply'),
|
|
Gtk.ResponseType.OK))
|
|
mpath = config.get('geography.path')
|
|
if not mpath:
|
|
mpath = HOME_DIR
|
|
selected_dir.set_current_folder(os.path.dirname(mpath))
|
|
|
|
status = selected_dir.run()
|
|
if status == Gtk.ResponseType.OK:
|
|
val = selected_dir.get_filename()
|
|
if val:
|
|
self.path_entry.set_text(val)
|
|
selected_dir.destroy()
|