FR10020: Add "within <n> km/miles/degree" filter (#382)
This commit is contained in:
parent
022da0cb82
commit
543661d62e
@ -3,6 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2002-2007 Donald N. Allingham
|
||||
# Copyright (C) 2007-2008 Brian G. Matherly
|
||||
# Copyright (C) 2017- 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
|
||||
@ -46,6 +47,7 @@ from ._matchessourceconfidence import MatchesSourceConfidence
|
||||
from ._changedsince import ChangedSince
|
||||
from ._hastag import HasTag
|
||||
from ._hastitle import HasTitle
|
||||
from ._withinarea import WithinArea
|
||||
|
||||
editor_rule_list = [
|
||||
AllPlaces,
|
||||
@ -68,5 +70,6 @@ editor_rule_list = [
|
||||
ChangedSince,
|
||||
HasTag,
|
||||
HasTitle,
|
||||
WithinArea,
|
||||
IsEnclosedBy
|
||||
]
|
||||
|
96
gramps/gen/filters/rules/place/_withinarea.py
Normal file
96
gramps/gen/filters/rules/place/_withinarea.py
Normal file
@ -0,0 +1,96 @@
|
||||
#
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2002-2006 Donald N. Allingham
|
||||
# Copyright (C) 2015 Nick Hall
|
||||
# Copyright (C) 2017- 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.
|
||||
#
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Standard Python modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from math import pi, cos, hypot
|
||||
from ....const import GRAMPS_LOCALE as glocale
|
||||
_ = glocale.translation.sgettext
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Gramps modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from .. import Rule
|
||||
from ....utils.location import located_in
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# WithinArea
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class WithinArea(Rule):
|
||||
"""
|
||||
Rule that checks for a place within an area
|
||||
"""
|
||||
|
||||
labels = [_('ID:'), _('Value:'), _('Units:')]
|
||||
name = _('Places within an area')
|
||||
description = _('Matches places within a given distance of another place')
|
||||
category = _('Position filters')
|
||||
|
||||
def prepare(self, db, user):
|
||||
ref_place = db.get_place_from_gramps_id(self.list[0])
|
||||
self.handle = None
|
||||
self.radius = None
|
||||
self.latitude = None
|
||||
self.longitude = None
|
||||
if ref_place:
|
||||
self.handle = ref_place.handle
|
||||
self.latitude = ref_place.get_latitude()
|
||||
if self.latitude == "":
|
||||
self.latitude = None
|
||||
return
|
||||
self.longitude = ref_place.get_longitude()
|
||||
value = self.list[1]
|
||||
unit = self.list[2]
|
||||
# earth perimeter in kilometers for latitude
|
||||
# 2 * pi * (6371 * cos(latitude/180*pi))
|
||||
# so 1 degree correspond to the result above / 360
|
||||
earth_perimeter = 2*pi*(6371*cos(float(self.latitude)/180*pi))
|
||||
if unit == 0: # kilometers
|
||||
self.radius = float(value / (earth_perimeter/360))
|
||||
elif unit == 1: # miles
|
||||
self.radius = float((value / (earth_perimeter/360))/0.62138)
|
||||
else: # degrees
|
||||
self.radius = float(value)
|
||||
self.radius = self.radius/2
|
||||
|
||||
def apply(self, db, place):
|
||||
if self.handle is None:
|
||||
return False
|
||||
if self.latitude is None:
|
||||
return False
|
||||
if self.longitude is None:
|
||||
return False
|
||||
if place:
|
||||
lat = place.get_latitude()
|
||||
lon = place.get_longitude()
|
||||
if lat and lon:
|
||||
if (hypot(float(self.latitude)-float(lat),
|
||||
float(self.longitude)-float(lon)) <= self.radius) == True:
|
||||
return True
|
||||
return False
|
@ -2,6 +2,7 @@
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2016 Tom Samstag
|
||||
# Copyright (C) 2017 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
|
||||
@ -33,7 +34,9 @@ from ..place import (
|
||||
AllPlaces, HasCitation, HasGallery, HasIdOf, RegExpIdOf, HasNote,
|
||||
HasNoteRegexp, HasReferenceCountOf, HasSourceCount, HasSourceOf,
|
||||
PlacePrivate, MatchesSourceConfidence, HasData, HasNoLatOrLon,
|
||||
InLatLonNeighborhood, ChangedSince, HasTag, HasTitle, IsEnclosedBy)
|
||||
InLatLonNeighborhood, ChangedSince, HasTag, HasTitle, IsEnclosedBy,
|
||||
WithinArea
|
||||
)
|
||||
|
||||
TEST_DIR = os.path.abspath(os.path.join(DATA_DIR, "tests"))
|
||||
EXAMPLE = os.path.join(TEST_DIR, "example.gramps")
|
||||
@ -217,6 +220,15 @@ class BaseTest(unittest.TestCase):
|
||||
b'V6ALQCZZFN996CO4D', b'OC6LQCXMKP6NUVYQD8', b'CUUKQC6BY5LAZXLXC6',
|
||||
b'PTFKQCKPHO2VC5SYKS', b'PHUJQCJ9R4XQO5Y0WS']))
|
||||
|
||||
def test_withinarea(self):
|
||||
"""
|
||||
Test within area rule.
|
||||
"""
|
||||
rule = WithinArea(['P1339', 100, 0])
|
||||
self.assertEqual(self.filter_with_rule(rule), set([
|
||||
b'KJUJQCY580EB77WIVO', b'TLVJQC4FD2CD9OYAXU', b'TE4KQCL9FDYA4PB6VW',
|
||||
b'W9GLQCSRJIQ9N2TGDF']))
|
||||
|
||||
def test_isenclosedby_inclusive(self):
|
||||
"""
|
||||
Test IsEnclosedBy rule with inclusive option.
|
||||
|
@ -581,6 +581,9 @@ class EditRule(ManagedWindow):
|
||||
long_days = displayer.long_days
|
||||
days_of_week = long_days[2:] + long_days[1:2]
|
||||
t = MyList(list(map(str, range(7))), days_of_week)
|
||||
elif v == _('Units:'):
|
||||
t = MyList([0, 1, 2],
|
||||
[_('kilometers'), _('miles'), _('degrees')])
|
||||
else:
|
||||
t = MyEntry()
|
||||
t.set_hexpand(True)
|
||||
|
@ -4,6 +4,7 @@
|
||||
# Copyright (C) 2002-2006 Donald N. Allingham
|
||||
# Copyright (C) 2008 Gary Burton
|
||||
# Copyright (C) 2010,2015 Nick Hall
|
||||
# Copyright (C) 2017- 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
|
||||
@ -46,7 +47,7 @@ from .. import build_filter_model
|
||||
from . import SidebarFilter
|
||||
from gramps.gen.filters import GenericFilterFactory, rules
|
||||
from gramps.gen.filters.rules.place import (RegExpIdOf, HasData, IsEnclosedBy,
|
||||
HasTag, HasNoteRegexp,
|
||||
HasTag, HasNoteRegexp, WithinArea,
|
||||
MatchesFilter)
|
||||
|
||||
GenericPlaceFilter = GenericFilterFactory('Place')
|
||||
@ -65,6 +66,7 @@ class PlaceSidebarFilter(SidebarFilter):
|
||||
self.filter_place = Place()
|
||||
self.filter_place.set_type((PlaceType.CUSTOM, ''))
|
||||
self.ptype = Gtk.ComboBox(has_entry=True)
|
||||
self.dbstate = dbstate
|
||||
if dbstate.is_open():
|
||||
self.custom_types = dbstate.db.get_place_types()
|
||||
else:
|
||||
@ -80,6 +82,7 @@ class PlaceSidebarFilter(SidebarFilter):
|
||||
self.filter_code = widgets.BasicEntry()
|
||||
self.filter_enclosed = widgets.PlaceEntry(dbstate, uistate, [])
|
||||
self.filter_note = widgets.BasicEntry()
|
||||
self.filter_within = widgets.PlaceWithin(dbstate, uistate, [])
|
||||
|
||||
self.filter_regex = Gtk.CheckButton(label=_('Use regular expressions'))
|
||||
self.tag = Gtk.ComboBox()
|
||||
@ -106,6 +109,7 @@ class PlaceSidebarFilter(SidebarFilter):
|
||||
self.add_entry(_('Type'), self.ptype)
|
||||
self.add_text_entry(_('Code'), self.filter_code)
|
||||
self.add_text_entry(_('Enclosed By'), self.filter_enclosed)
|
||||
self.add_text_entry(_('Within'), self.filter_within)
|
||||
self.add_text_entry(_('Note'), self.filter_note)
|
||||
self.add_entry(_('Tag'), self.tag)
|
||||
self.add_filter_entry(_('Custom filter'), self.generic)
|
||||
@ -117,6 +121,7 @@ class PlaceSidebarFilter(SidebarFilter):
|
||||
self.filter_code.set_text('')
|
||||
self.filter_enclosed.set_text('')
|
||||
self.filter_note.set_text('')
|
||||
self.filter_within.set_value(0, 0)
|
||||
self.ptype.get_child().set_text('')
|
||||
self.tag.set_active(0)
|
||||
self.generic.set_active(0)
|
||||
@ -128,12 +133,13 @@ class PlaceSidebarFilter(SidebarFilter):
|
||||
code = str(self.filter_code.get_text()).strip()
|
||||
enclosed = str(self.filter_enclosed.get_text()).strip()
|
||||
note = str(self.filter_note.get_text()).strip()
|
||||
within = self.filter_within.get_value()
|
||||
regex = self.filter_regex.get_active()
|
||||
tag = self.tag.get_active() > 0
|
||||
gen = self.generic.get_active() > 0
|
||||
|
||||
empty = not (gid or name or ptype or code or enclosed or note or regex
|
||||
or tag or gen)
|
||||
or within or tag or gen)
|
||||
if empty:
|
||||
generic_filter = None
|
||||
else:
|
||||
@ -153,6 +159,15 @@ class PlaceSidebarFilter(SidebarFilter):
|
||||
rule = HasNoteRegexp([note], use_regex=regex)
|
||||
generic_filter.add_rule(rule)
|
||||
|
||||
if within and within[0] > 0 and self.dbstate.is_open():
|
||||
rule = WithinArea([None, within[0], within[1]])
|
||||
active_ref = self.uistate.get_active('Place')
|
||||
if active_ref:
|
||||
place = self.dbstate.db.get_place_from_handle(active_ref)
|
||||
gid = place.get_gramps_id()
|
||||
rule = WithinArea([gid, within[0], within[1]])
|
||||
generic_filter.add_rule(rule)
|
||||
|
||||
# check the Tag
|
||||
if tag:
|
||||
model = self.tag.get_model()
|
||||
|
@ -3,6 +3,7 @@
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2008 Zsolt Foldvari
|
||||
# Copyright (C) 2017 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
|
||||
@ -44,3 +45,4 @@ from .validatedcomboentry import *
|
||||
from .validatedmaskedentry import *
|
||||
from .valueaction import *
|
||||
from .valuetoolitem import *
|
||||
from .placewithin import *
|
||||
|
103
gramps/gui/widgets/placewithin.py
Normal file
103
gramps/gui/widgets/placewithin.py
Normal file
@ -0,0 +1,103 @@
|
||||
#
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2015 Nick Hall
|
||||
# Copyright (C) 2017- 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.
|
||||
#
|
||||
|
||||
__all__ = ["PlaceWithin"]
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Standard python modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
import logging
|
||||
_LOG = logging.getLogger(".widgets.placewithin")
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# GTK/Gnome modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from gi.repository import Gtk
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Gramps modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from ..selectors import SelectorFactory
|
||||
from gramps.gen.display.place import displayer as _pd
|
||||
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
||||
_ = glocale.translation.gettext
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# PlaceWithin class
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class PlaceWithin(Gtk.Box):
|
||||
|
||||
def __init__(self, dbstate, uistate, track):
|
||||
Gtk.Box.__init__(self)
|
||||
self.dbstate = dbstate
|
||||
self.uistate = uistate
|
||||
self.track = track
|
||||
self.last = ""
|
||||
# initial tooltip when no place already selected.
|
||||
self.tooltip = _('Matches places within a given distance'
|
||||
' of the active place. You have no active place.')
|
||||
self.set_tooltip_text(self.tooltip)
|
||||
self.entry = Gtk.Entry()
|
||||
self.entry.set_max_length(3)
|
||||
self.entry.set_width_chars(5)
|
||||
self.entry.connect('changed', self.entry_change)
|
||||
self.pack_start(self.entry, True, True, 0)
|
||||
self.unit = Gtk.ComboBoxText()
|
||||
list(map(self.unit.append_text,
|
||||
[ _('kilometers'), _('miles'), _('degrees') ]))
|
||||
self.unit.set_active(0)
|
||||
self.pack_start(self.unit, False, True, 0)
|
||||
self.show_all()
|
||||
|
||||
def get_value(self):
|
||||
value = self.entry.get_text()
|
||||
if value == "":
|
||||
value = "0"
|
||||
return int(value), self.unit.get_active()
|
||||
|
||||
def set_value(self, value, unit):
|
||||
self.entry.set_text(str(value))
|
||||
self.unit.set_active(int(unit))
|
||||
|
||||
def entry_change(self, entry):
|
||||
value = entry.get_text()
|
||||
if value.isnumeric() or value == "":
|
||||
self.last = value # This entry is numeric and valid.
|
||||
else:
|
||||
entry.set_text(self.last) # reset to the last valid entry
|
||||
_db = self.dbstate.db
|
||||
active_reference = self.uistate.get_active('Place')
|
||||
place_name = None
|
||||
if active_reference:
|
||||
place = _db.get_place_from_handle(active_reference)
|
||||
place_name = _pd.display(self.dbstate.db, place)
|
||||
if place_name is None:
|
||||
self.set_tooltip_text(self.tooltip)
|
||||
else:
|
||||
self.set_tooltip_text(place_name)
|
Loading…
Reference in New Issue
Block a user