diff --git a/src/PlaceUtils.py b/src/PlaceUtils.py index fa125b973..eaa64fe2c 100644 --- a/src/PlaceUtils.py +++ b/src/PlaceUtils.py @@ -1,9 +1,10 @@ -# -*- python -*- +#!/usr/bin/env python # -*- coding: utf-8 -*- # # Gramps - a GTK+/GNOME based genealogy program # -# Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2007-2009 B. Malengier +# Copyright (C) 2009 Swoon on bug tracker # # 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 @@ -22,7 +23,6 @@ # $Id:PlaceUtils.py 9912 2008-01-22 09:17:46Z acraphae $ -# Written by Benny Malengier #------------------------------------------------------------------------- # @@ -31,6 +31,12 @@ #------------------------------------------------------------------------- from gettext import gettext as _ +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- + #------------------------------------------------------------------------- # @@ -66,6 +72,193 @@ if 'N' == South or 'S' == North or 'E' == West or 'W' == East: translate_en_loc['W'] = 'W' # end localisation part + +#------------------ +# +# helper functions +# +#------------------ + +def __convert_structure_to_float(sign, degs, mins=0, secs=0.0) : + """helper function which converts a structure to a nice + representation + """ + v = float(degs) + if mins is not None: + v += float(mins) / 60. + if secs is not None: + v += secs / 3600. + if sign == "-": + v = v * -1. + + return v + +def __convert_using_float_repr(stringValue): + """ helper function that tries to convert the string using the float + representation + """ + try : + v = float(stringValue) + return v + except ValueError : + return None; + +def __convert_using_colon_repr(stringValue): + """ helper function that tries to convert the string using the colon + representation + """ + if stringValue.find(r':') == -1 : + return None + + l = stringValue.split(':') + if len(l) < 2 or len(l) > 3: + return None + l[0]=l[0].strip() + # if no characters before ':' nothing useful is input! + if len(l[0]) == 0: + return None + if l[0][0] == '-': + sign = '-' + l[0]=l[0][1:] + else: + sign = '+' + try: + degs = int(l[0]) + if degs < 0: + return None + except: + return None + try: + mins = int(l[1]) + if mins < 0 or mins >= 60: + return None + except: + return None + secs=0. + if len(l) == 3: + try: + secs = float(l[2]) + if secs < 0. or secs >= 60.: + return None + except: + return None + + return __convert_structure_to_float(sign, degs, mins, secs) + +def __convert_using_classic_repr(stringValue, typedeg): + """helper function that tries to convert the string using the colon + representation + """ + if stringValue.find(r'_') != -1: + return None # not a valid lat or lon + + #exchange some characters + stringValue = stringValue.replace(u'°',r'_') + #allow to input ° as # + stringValue = stringValue.replace(r'#',r'_') + #allow to input " as '' + stringValue = stringValue.replace(r"''",r'"') + #allow some special unicode symbols + stringValue = stringValue.replace(u'\u2033',r'"') + stringValue = stringValue.replace(u'\u2032',r"'") + #ignore spaces, a regex with \s* would be better here... + stringValue = stringValue.replace(r' ', r'') + stringValue = stringValue.replace(r'\t', r'') + + # get the degrees, must be present + if stringValue.find(r'_') == -1: + return None + l = stringValue.split(r'_') + if len(l) != 2: + return None + + try: + degs = int(l[0]) #degrees must be integer value + if degs < 0: + return None + except: + return None + # next: minutes might be present once + l2 = l[1].split(r"'") + l3 = l2 + mins = 0 + if len(l2) > 2: + return None + if len(l2) == 2: + l3 = [l2[1],] + try: + mins = int(l2[0]) #minutes must be integer value + if mins < 0 or mins >= 60: + return None + except: + return None + # next: seconds might be present once + l3 = l3[0].split(r'"') + last = l3[0] + secs = 0. + if len(l3) > 2: + return None + if len(l3) == 2: + last = l3[1] + try: + secs = float(l3[0]) + if secs < 0. or secs >= 60.: + return None + except: + return None + # last entry should be the direction + if typedeg == 'lat': + if last == 'N': + sign = '+' + elif last == 'S': + sign = '-' + else: + return None + elif typedeg == 'lon': + if last == 'E': + sign = '+' + elif last == 'W': + sign = '-' + else: + return None + else: + return None + + return __convert_structure_to_float(sign, degs, mins, secs) + +def __convert_float_val(val, typedeg = "lat"): + # function converting input to float, recognizing decimal input, or + # degree notation input. Only english input + # There is no check on maximum/minimum of degree + # In case of degree minutes seconds direction input, + # it is checked that degree >0, 0<= minutes <= 60, + # 0<= seconds <= 60, direction is in the directions dic. + + #change , to . so that , input works in non , localization + #this is no problem, as a number like 100,000.20 cannot appear in + #lat/lon + #change XX,YY into XX.YY + if val.find(r'.') == -1 : + val = val.replace(u',', u'.') + + # format: XX.YYYY + v = __convert_using_float_repr(val) + if v is not None: + return v + + # format: XX:YY:ZZ + v = __convert_using_colon_repr(val) + if v is not None : + return v + + # format: XX° YY' ZZ" [NSWE] + v = __convert_using_classic_repr(val, typedeg) + if v is not None : + return v + + # no format succeeded + return None + #------------------------------------------------------------------------- # # conversion function @@ -97,175 +290,6 @@ def conv_lat_lon(latitude, longitude, format="D.D4"): and -180 180th meridian """ - #------------------------------------------------------------------------- - # begin internal function converting one input to float - #------------------------------------------------------------------------- - def convert_float_val(val, typedeg = "lat"): - # function converting input to float, recognizing decimal input, or - # degree notation input. Only english input - # There is no check on maximum/minimum of degree - # In case of degree minutes seconds direction input, - # it is checked that degree >0, 0<= minutes <= 60, - # 0<= seconds <= 60, direction is in the directions dic. - v = None - sign = None - secs = None - mins = None - degs = None - error = False - - #change , to . so that , input works in non , localization - #this is no problem, as a number like 100,000.20 cannot appear in - #lat/lon - try : - test = float('10,11') - except ValueError : - #change 10,11 into 10.11 - #if point is already present in val, we can do nothing - if val.find(r'.') == -1 : - val = val.replace( r',',r'.') - try : - test = float('10.11') - except ValueError : - #change 10.11 into 10,11 - #if comma is already present in val, we can do nothing - if val.find(r',') == -1 : - val = val.replace( r'.',r',') - - try: - v = float(val) #decimal notation, now float - except ValueError: - # look for : notation - if val.find(r':') != -1 : - l = val.split(':') - if len(l) < 2 or len(l) > 3: - error = True - l[0]=l[0].strip() - # if no characters before ':' nothing useful is input! - if len(l[0]) == 0: - return None - if l[0][0] == '-': - sign = '-' - l[0]=l[0][1:] - else: - sign = '+' - try: - degs = int(l[0]) - if degs < 0: - error = True - except: - error = True - try: - mins = int(l[1]) - if mins < 0 or mins >= 60: - error = True - except: - error = True - secs=0. - if len(l) == 3: - try: - secs = float(l[2]) - if secs < 0. or secs >= 60.: - error = True - except: - error = True - - # if nothing found yet, look for classical notation - if val.find(r'_') != -1: - error = True # not a valid lat or lon - val = val.replace( r'°',r'_') - #allow to input ° as # - val = val.replace( r'#',r'_') - #allow to input " as '' - val = val.replace( r"''",r'"') - #allow some special unicode symbols - val = val.replace( u'\u2033',r'"') - val = val.replace( u'\u2032',r"'") - #ignore spaces, a regex with \s* would be better here... - val = val.replace(r' ', r'') - val = val.replace(r'\t', r'') - # get the degrees, must be present to parse as old degree notation - if val.find(r'_') != -1: - l = val.split('_') - if len(l) != 2: - error = True - else: - try: - degs = int(l[0]) #degrees must be integer value - if degs < 0: - error = True - except: - error = True - # next: minutes might be present once - l2 = l[1].split(r"'") - l3 = l2 - mins = 0 - if len(l2) > 2: - error = True - if len(l2) == 2: - l3 = [l2[1],] - try: - mins = int(l2[0]) #minutes must be integer value - if mins < 0 or mins >= 60: - error = True - except: - error = True - # next: seconds might be present once - l3 = l3[0].split(r'"') - last = l3[0] - secs = 0. - if len(l3) > 2: - error = True - if len(l3) == 2: - last = l3[1] - try: - secs = float(l3[0]) - if secs < 0. or secs >= 60.: - error = True - except: - error = True - # last entry should be the direction - last = last.strip() #remove leading/trailing spaces - if typedeg == 'lat': - if last == 'N': - sign = '+' - elif last == 'S': - sign = '-' - else: - error = True - if typedeg == 'lon': - if last == 'E': - sign = '+' - elif last == 'W': - sign = '-' - else: - error = True - # degs should have a value now - if degs is None: - error = True - - if error: - return None - if v is not None: - return v - #we have a degree notation, convert to float - v = float(degs) - if secs is not None: - v += secs / 3600. - if mins is not None: - v += float(mins) / 60. - if sign =="-": - v = v * -1. - - return v - #------------------------------------------------------------------------- - # end internal function converting one input to float - #------------------------------------------------------------------------- - - #------------------------------------------------------------------------- - # begin convert function - #------------------------------------------------------------------------- - # we start the function changing latitude/longitude in english if latitude.find('N') == -1 and latitude.find('S') == -1: # entry is not in english, convert to english @@ -277,8 +301,8 @@ def conv_lat_lon(latitude, longitude, format="D.D4"): longitude = longitude.replace(translate_en_loc['E'],'E') # convert to float - lat_float = convert_float_val(latitude, 'lat') - lon_float = convert_float_val(longitude, 'lon') + lat_float = __convert_float_val(latitude, 'lat') + lon_float = __convert_float_val(longitude, 'lon') # give output (localized if needed) if lat_float is None or lon_float is None: @@ -365,7 +389,7 @@ def conv_lat_lon(latitude, longitude, format="D.D4"): else: str_lon = ("%d°%02d'%05.2f\"" % (deg_lon, min_lon+1, 0.)) \ + dir_lon - + return (str_lat, str_lon) if format == "DEG-:": @@ -441,9 +465,6 @@ def conv_lat_lon(latitude, longitude, format="D.D4"): str_lon = sign_lon + \ ("%03d%02d%06.3f" % (deg_lon, min_lon+1, 0.)) return str_lat + str_lon - #------------------------------------------------------------------------- - # end convert function - #------------------------------------------------------------------------- @@ -486,7 +507,7 @@ if __name__ == '__main__': lat, lon = '50.849888888888', '2.885897222222' test_formats_success(lat,lon) - lat, lon = ' 50°50\'59.60"N', ' 2°53\'9.23"E' + lat, lon = u' 50°50\'59.60"N', u' 2°53\'9.23"E' test_formats_success(lat,lon) lat, lon = ' 50 : 50 : 59.60 ', ' -2:53 : 9.23 ' test_formats_success(lat,lon) @@ -494,24 +515,24 @@ if __name__ == '__main__': test_formats_fail(lat,lon) lat, lon = ' 50:50: 59.60', ' d u m my' test_formats_fail(lat,lon) - lat, lon = ' 50°59.60"N', ' 2°53\'E' + lat, lon = u' 50°59.60"N', u' 2°53\'E' test_formats_success(lat,lon) - lat, lon = ' 11° 11\' 11" N, 11° 11\' 11" O', ' ' + lat, lon = u' 11° 11\' 11" N, 11° 11\' 11" O', ' ' test_formats_fail(lat,lon) # very small negative lat, lon = '-0.00006', '-0.00006' test_formats_success(lat,lon) # missing direction N/S - lat, lon = ' 50°59.60"', ' 2°53\'E' + lat, lon = u' 50°59.60"', u' 2°53\'E' test_formats_fail(lat,lon) # wrong direction on latitude - lat, lon = ' 50°59.60"E', ' 2°53\'N' + lat, lon = u' 50°59.60"E', u' 2°53\'N' test_formats_fail(lat,lon) # same as above - lat, lon = ' 50°59.99"E', ' 2°59\'59.99"N' + lat, lon = u' 50°59.99"E', u' 2°59\'59.99"N' test_formats_fail(lat,lon) # test precision - lat, lon = ' 50°59.99"S', ' 2°59\'59.99"E' + lat, lon = u' 50°59.99"S', u' 2°59\'59.99"E' test_formats_success(lat,lon) # to large value of lat lat, lon = '90.849888888888', '2.885897222222' @@ -520,55 +541,55 @@ if __name__ == '__main__': lat, lon = '90', '-180' test_formats_success(lat,lon) # extreme values allowed - lat, lon = '90° 00\' 00.00" S ', '179° 59\'59.99"W' + lat, lon = u'90° 00\' 00.00" S ', u'179° 59\'59.99"W' test_formats_success(lat,lon) # extreme value not allowed - lat, lon = '90° 00\' 00.00" N', '180° 00\'00.00" E' + lat, lon = u'90° 00\' 00.00" N', u'180° 00\'00.00" E' test_formats_fail(lat,lon) # extreme values allowed lat, lon = '90: 00: 00.00 ', '-179: 59:59.99' test_formats_success(lat,lon) # extreme value not allowed - lat, lon = '90° 00\' 00.00" N', '180:00:00.00' + lat, lon = u'90° 00\' 00.00" N', '180:00:00.00' test_formats_fail(lat,lon) # extreme values not allowed lat, lon = '90', '180' test_formats_fail(lat,lon) - lat, lon = ' 89°59\'60"N', ' 2°53\'W' + lat, lon = u' 89°59\'60"N', u' 2°53\'W' test_formats_fail(lat,lon) - lat, lon = ' 89°60\'00"N', ' 2°53\'W' + lat, lon = u' 89°60\'00"N', u' 2°53\'W' test_formats_fail(lat,lon) - lat, lon = ' 89.1°40\'00"N', ' 2°53\'W' + lat, lon = u' 89.1°40\'00"N', u' 2°53\'W' test_formats_fail(lat,lon) - lat, lon = ' 89°40\'00"N', ' 2°53.1\'W' + lat, lon = u' 89°40\'00"N', u' 2°53.1\'W' test_formats_fail(lat,lon) lat, lon = '0', '0' test_formats_success(lat,lon, "Special 0 value, crossing 0-meridian and equator") # small values close to equator - lat, lon = ' 1°1"N', ' 1°1\'E' + lat, lon = u' 1°1"N', u' 1°1\'E' test_formats_success(lat,lon) # roundoff - lat, lon = ' 1°59.999"N', ' 1°59.999"E' + lat, lon = u' 1°59.999"N', u' 1°59.999"E' test_formats_success(lat,lon,'Examples of round off and how it behaves') - lat, lon = ' 1°59\'59.9999"N', ' 1°59\'59.9999"E' + lat, lon = u' 1°59\'59.9999"N', u' 1°59\'59.9999"E' test_formats_success(lat,lon,'Examples of round off and how it behaves') - lat, lon = '89°59\'59.9999"S', '179°59\'59.9999"W' + lat, lon = u'89°59\'59.9999"S', u'179°59\'59.9999"W' test_formats_success(lat,lon,'Examples of round off and how it behaves') - lat, lon = '89°59\'59.9999"N', '179°59\'59.9999"E' + lat, lon = u'89°59\'59.9999"N', u'179°59\'59.9999"E' test_formats_success(lat,lon,'Examples of round off and how it behaves') #insane number of decimals: - lat, lon = '89°59\'59.99999999"N', '179°59\'59.99999999"E' + lat, lon = u'89°59\'59.99999999"N', u'179°59\'59.99999999"E' test_formats_success(lat,lon,'Examples of round off and how it begaves') #recognise '' as seconds " - lat, lon = '89°59\'59.99\'\' N', '179°59\'59.99\'\'E' + lat, lon = u'89°59\'59.99\'\' N', u'179°59\'59.99\'\'E' test_formats_success(lat,lon, "input \" as ''") #test localisation of , and . as delimiter lat, lon = '50.849888888888', '2,885897222222' test_formats_success(lat,lon, 'localisation of . and , ') - lat, lon = '89°59\'59.9999"S', '179°59\'59,9999"W' + lat, lon = u'89°59\'59.9999"S', u'179°59\'59,9999"W' test_formats_success(lat,lon, 'localisation of . and , ') - lat, lon = '89°59\'1.599,999"S', '179°59\'59,9999"W' + lat, lon = u'89°59\'1.599,999"S', u'179°59\'59,9999"W' test_formats_fail(lat,lon, 'localisation of . and , ') #rest lat, lon = '81.2', '-182.3' @@ -591,8 +612,10 @@ if __name__ == '__main__': test_formats_success(lat,lon) lat, lon = '+50: 0 : 1 : 1', '-2:1:2' test_formats_fail(lat,lon) - lat, lon = '+61° 43\' 60.00"', '+17° 7\' 60.00"' + lat, lon = u'+61° 43\' 60.00"', u'+17° 7\' 60.00"' test_formats_fail(lat,lon) - lat, lon = '+61° 44\' 00.00"N', '+17° 8\' 00.00"E' + lat, lon = u'+61° 44\' 00.00"N', u'+17° 8\' 00.00"E' test_formats_success(lat,lon) + lat, lon = ': 0 : 1 : 1', ':1:2' + test_formats_fail(lat,lon)