4
@brief Misc utilities for wxGUI
6
(C) 2007-2013 by the GRASS Development Team
8
This program is free software under the GNU General Public License
9
(>=v2). Read the file COPYING that comes with GRASS for details.
11
@author Martin Landa <landa.martin gmail.com>
12
@author Jachym Cepicky
24
from grass.script import core as grass
25
from grass.script import task as gtask
27
from core import globalvar
28
from core.gcmd import RunCommand
29
from core.debug import Debug
32
# intended to be used also outside this module
34
_ = gettext.translation('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale')).ugettext
36
# using no translation silently
37
def null_gettext(string):
41
def normalize_whitespace(text):
42
"""Remove redundant whitespace from a string"""
43
return string.join(string.split(text), ' ')
46
"""Platform spefic shlex.split"""
48
if sys.platform == "win32":
49
return shlex.split(s.replace('\\', r'\\'))
52
except ValueError as e:
53
sys.stderr.write(_("Syntax error: %s") % e)
57
def GetTempfile(pref=None):
58
"""Creates GRASS temporary file using defined prefix.
61
Fix path on MS Windows/MSYS
63
:param pref: prefer the given path
65
:return: Path to file name (string) or None
67
ret = RunCommand('g.tempfile',
71
tempfile = ret.splitlines()[0].strip()
74
# ugly hack for MSYS (MS Windows)
75
if platform.system() == 'Windows':
76
tempfile = tempfile.replace("/", "\\")
78
path, file = os.path.split(tempfile)
80
return os.path.join(pref, file)
86
def GetLayerNameFromCmd(dcmd, fullyQualified = False, param = None,
88
"""Get map name from GRASS command
90
Parameter dcmd can be modified when first parameter is not
93
:param dcmd: GRASS command (given as list)
94
:param fullyQualified: change map name to be fully qualified
95
:param param: params directory
96
:param str layerType: check also layer type ('raster', 'vector',
99
:return: tuple (name, found)
105
return mapname, False
107
if 'd.grid' == dcmd[0]:
109
elif 'd.geodesic' in dcmd[0]:
111
elif 'd.rhumbline' in dcmd[0]:
113
elif 'd.graph' in dcmd[0]:
117
for idx in range(len(dcmd)):
119
p, v = dcmd[idx].split('=', 1)
124
params = [(idx, p, v)]
127
# this does not use types, just some (incomplete subset of?) names
128
if p in ('map', 'input', 'layer',
129
'red', 'blue', 'green',
130
'hue', 'saturation', 'intensity',
132
params.append((idx, p, v))
138
if '=' not in dcmd[i] and not dcmd[i].startswith('-'):
139
task = gtask.parse_interface(dcmd[0])
140
# this expects the first parameter to be the right one
141
p = task.get_options()['params'][0].get('name', '')
142
params.append((i, p, dcmd[i]))
146
return mapname, False
149
return mapname, False
151
# need to add mapset for all maps
153
for i, p, v in params:
158
if fullyQualified and '@' not in mapname:
159
if layerType in ('raster', 'vector', '3d-raster', 'rgb', 'his'):
161
if layerType in ('raster', 'rgb', 'his'):
165
mapset = grass.find_file(mapname, element = findType)['mapset']
166
except AttributeError: # not found
171
mapset = '' # grass.gisenv()['MAPSET']
175
for i, p, v in params:
178
dcmd[i] = p + '=' + v
179
if i in mapsets and mapsets[i]:
180
dcmd[i] += '@' + mapsets[i]
184
for i, p, v in params:
185
if v.lower().rfind('@ogr') > -1:
187
if p == 'layer' and not ogr:
189
maps.append(dcmd[i].split('=', 1)[1])
191
mapname = '\n'.join(maps)
193
return mapname, found
195
def GetValidLayerName(name):
196
"""Make layer name SQL compliant, based on G_str_to_sql()
199
Better use directly Ctypes to reuse venerable libgis C fns...
201
retName = name.strip()
203
# check if name is fully qualified
205
retName, mapset = retName.split('@')
210
retNameList = list(retName)
211
for c in retNameList:
212
if not (c >= 'A' and c <= 'Z') and \
213
not (c >= 'a' and c <= 'z') and \
214
not (c >= '0' and c <= '9'):
215
retNameList[cIdx] = '_'
217
retName = ''.join(retNameList)
219
if not (retName[0] >= 'A' and retName[0] <= 'Z') and \
220
not (retName[0] >= 'a' and retName[0] <= 'z'):
221
retName = 'x' + retName[1:]
224
retName = retName + '@' + mapset
228
def ListOfCatsToRange(cats):
229
"""Convert list of category number to range(s)
231
Used for example for d.vect cats=[range]
233
:param cats: category list
235
:return: category range string
242
cats = map(int, cats)
251
if cats[i + next] == cats[j] - 1:
258
catstr += '%d-%d,' % (cats[i], cats[i + next])
261
catstr += '%d,' % (cats[i])
264
return catstr.strip(',')
266
def ListOfMapsets(get = 'ordered'):
267
"""Get list of available/accessible mapsets
269
:param str get: method ('all', 'accessible', 'ordered')
271
:return: list of mapsets
272
:return: None on error
276
if get == 'all' or get == 'ordered':
277
ret = RunCommand('g.mapsets',
284
mapsets = ret.splitlines()
285
ListSortLower(mapsets)
289
if get == 'accessible' or get == 'ordered':
290
ret = RunCommand('g.mapsets',
296
if get == 'accessible':
297
mapsets = ret.splitlines()
299
mapsets_accessible = ret.splitlines()
300
for mapset in mapsets_accessible:
301
mapsets.remove(mapset)
302
mapsets = mapsets_accessible + mapsets
308
def ListSortLower(list):
309
"""Sort list items (not case-sensitive)"""
310
list.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
312
def GetVectorNumberOfLayers(vector):
313
"""Get list of all vector layers"""
318
fullname = grass.find_file(name = vector, element = 'vector')['fullname']
320
Debug.msg(5, "utils.GetVectorNumberOfLayers(): vector map '%s' not found" % vector)
323
ret, out, msg = RunCommand('v.category',
329
sys.stderr.write(_("Vector map <%(map)s>: %(msg)s\n") % { 'map' : fullname, 'msg' : msg })
332
Debug.msg(1, "GetVectorNumberOfLayers(): ret %s" % ret)
334
for layer in out.splitlines():
337
Debug.msg(3, "utils.GetVectorNumberOfLayers(): vector=%s -> %s" % \
338
(fullname, ','.join(layers)))
342
def Deg2DMS(lon, lat, string = True, hemisphere = True, precision = 3):
343
"""Convert deg value to dms string
345
:param lon: longitude (x)
346
:param lat: latitude (y)
347
:param string: True to return string otherwise tuple
348
:param hemisphere: print hemisphere
349
:param precision: seconds precision
351
:return: DMS string or tuple of values
352
:return: empty string on error
388
slat = __ll_parts(flat, precision = precision)
389
slon = __ll_parts(flon, precision = precision)
392
return slon + hlon + '; ' + slat + hlat
394
return (slon + hlon, slat + hlat)
396
def DMS2Deg(lon, lat):
397
"""Convert dms value to deg
399
:param lon: longitude (x)
400
:param lat: latitude (y)
402
:return: tuple of converted values
403
:return: ValueError on error
405
x = __ll_parts(lon, reverse = True)
406
y = __ll_parts(lat, reverse = True)
410
def __ll_parts(value, reverse = False, precision = 3):
411
"""Converts deg to d:m:s string
413
:param value: value to be converted
414
:param reverse: True to convert from d:m:s to deg
415
:param precision: seconds precision (ignored if reverse is True)
417
:return: converted value (string/float)
418
:return: ValueError on error (reverse == True)
422
return '%s%.*f' % ('00:00:0', precision, 0.0)
425
m = int((value - d) * 60)
426
s = ((value - d) * 60 - m) * 60
436
s = '0%.*f' % (precision, s)
438
s = '%.*f' % (precision, s)
440
return str(d) + ':' + m + ':' + s
443
d, m, s = value.split(':')
448
d, m = value.split(':')
462
if hs not in ('N', 'S', 'E', 'W'):
470
fs = float(s) / (60 * 60)
472
return coef * (float(d) + fm + fs)
474
def GetCmdString(cmd):
475
"""Get GRASS command as string.
477
:param cmd: GRASS command given as tuple
479
:return: command string
481
return ' '.join(CmdTupleToList(cmd))
483
def CmdTupleToList(cmd):
484
"""Convert command tuple to list.
486
:param cmd: GRASS command given as tuple
488
:return: command in list
494
cmdList.append(cmd[0])
496
if 'flags' in cmd[1]:
497
for flag in cmd[1]['flags']:
498
cmdList.append('-' + flag)
499
for flag in ('help', 'verbose', 'quiet', 'overwrite'):
500
if flag in cmd[1] and cmd[1][flag] is True:
501
cmdList.append('--' + flag)
503
for k, v in cmd[1].iteritems():
504
if k in ('flags', 'help', 'verbose', 'quiet', 'overwrite'):
506
cmdList.append('%s=%s' % (k, v))
511
"""Convert command list to tuple for gcmd.RunCommand()"""
517
if '=' in item: # params
518
key, value = item.split('=', 1)
519
dcmd[str(key)] = str(value).replace('"', '')
520
elif item[:2] == '--': # long flags
522
if flag in ('help', 'verbose', 'quiet', 'overwrite'):
523
dcmd[str(flag)] = True
524
elif len(item) == 2 and item[0] == '-': # -> flags
525
if 'flags' not in dcmd:
527
dcmd['flags'] += item[1]
528
else: # unnamed parameter
529
module = gtask.parse_interface(cmd[0])
530
dcmd[module.define_first()] = item
532
return (cmd[0], dcmd)
535
"""Check path created by os.path.join"""
536
path = os.path.join(*args)
537
if platform.system() == 'Windows' and \
539
return path[1].upper() + ':\\' + path[3:].replace('/', '\\')
543
def ReadEpsgCodes(path):
544
"""Read EPSG code from the file
546
:param path: full path to the file with EPSG codes
548
:return: dictionary of EPSG code
549
:return: string on error
551
epsgCodeDict = dict()
556
return _("failed to open '%s'" % path)
559
for line in f.readlines():
565
descr = line[1:].strip()
567
code, params = line.split(" ", 1)
569
code = int(code.replace('<', '').replace('>', ''))
570
except ValueError as e:
574
epsgCodeDict[code] = (descr, params)
578
except StandardError as e:
583
def ReprojectCoordinates(coord, projOut, projIn = None, flags = ''):
584
"""Reproject coordinates
586
:param coord: coordinates given as tuple
587
:param projOut: output projection
588
:param projIn: input projection (use location projection settings)
590
:return: reprojected coordinates (returned as tuple)
592
coors = RunCommand('m.proj',
598
stdin = '%f;%f' % (coord[0], coord[1]),
601
coors = coors.split(';')
605
proj = projOut.split(' ')[0].split('=')[1]
608
if proj in ('ll', 'latlong', 'longlat') and 'd' not in flags:
609
return (proj, (e, n))
612
return (proj, (float(e), float(n)))
618
def GetListOfLocations(dbase):
619
"""Get list of GRASS locations in given dbase
621
:param dbase: GRASS database path
623
:return: list of locations (sorted)
625
listOfLocations = list()
628
for location in glob.glob(os.path.join(dbase, "*")):
630
if os.path.join(location, "PERMANENT") in glob.glob(os.path.join(location, "*")):
631
listOfLocations.append(os.path.basename(location))
634
except UnicodeEncodeError as e:
637
ListSortLower(listOfLocations)
639
return listOfLocations
641
def GetListOfMapsets(dbase, location, selectable = False):
642
"""Get list of mapsets in given GRASS location
644
:param dbase: GRASS database path
645
:param location: GRASS location
646
:param selectable: True to get list of selectable mapsets, otherwise all
648
:return: list of mapsets - sorted (PERMANENT first)
650
listOfMapsets = list()
653
ret = RunCommand('g.mapset',
662
for line in ret.rstrip().splitlines():
663
listOfMapsets += line.split(' ')
665
for mapset in glob.glob(os.path.join(dbase, location, "*")):
666
if os.path.isdir(mapset) and \
667
os.path.isfile(os.path.join(dbase, location, mapset, "WIND")):
668
listOfMapsets.append(os.path.basename(mapset))
670
ListSortLower(listOfMapsets)
673
def GetColorTables():
674
"""Get list of color tables"""
675
ret = RunCommand('r.colors',
681
return ret.splitlines()
683
def _getGDALFormats():
684
"""Get dictionary of avaialble GDAL drivers"""
686
ret = grass.read_command('r.in.gdal',
692
return _parseFormats(ret), _parseFormats(ret, writableOnly = True)
694
def _getOGRFormats():
695
"""Get dictionary of avaialble OGR drivers"""
697
ret = grass.read_command('v.in.ogr',
703
return _parseFormats(ret), _parseFormats(ret, writableOnly = True)
705
def _parseFormats(output, writableOnly = False):
706
"""Parse r.in.gdal/v.in.ogr -f output"""
707
formats = { 'file' : list(),
717
patt = re.compile('\(rw\+?\)$', re.IGNORECASE)
719
for line in output.splitlines():
720
key, name = map(lambda x: x.strip(), line.strip().rsplit(':', -1))
722
if writableOnly and not patt.search(key):
725
if name in ('Memory', 'Virtual Raster', 'In Memory Raster'):
727
if name in ('PostgreSQL', 'SQLite',
728
'ODBC', 'ESRI Personal GeoDatabase',
730
'PostGIS WKT Raster driver',
731
'PostGIS Raster driver',
735
formats['database'].append(name)
736
elif name in ('GeoJSON',
737
'OGC Web Coverage Service',
738
'OGC Web Map Service',
741
'HTTP Fetching Wrapper'):
742
formats['protocol'].append(name)
744
formats['file'].append(name)
746
for items in formats.itervalues():
753
def GetFormats(writableOnly = False):
754
"""Get GDAL/OGR formats"""
757
gdalAll, gdalWritable = _getGDALFormats()
758
ogrAll, ogrWritable = _getOGRFormats()
765
'gdal' : gdalWritable,
771
return formats['writable']
773
return formats['all']
776
rasterFormatExtension = {
778
'Erdas Imagine Images (.img)' : 'img',
779
'Ground-based SAR Applications Testbed File Format (.gff)' : 'gff',
780
'Arc/Info Binary Grid' : 'adf',
781
'Portable Network Graphics' : 'png',
783
'Japanese DEM (.mem)' : 'mem',
784
'Graphics Interchange Format (.gif)' : 'gif',
785
'X11 PixMap Format' : 'xpm',
786
'MS Windows Device Independent Bitmap' : 'bmp',
787
'SPOT DIMAP' : 'dim',
788
'RadarSat 2 XML Product' : 'xml',
789
'EarthWatch .TIL' : 'til',
790
'ERMapper .ers Labelled' : 'ers',
791
'ERMapper Compressed Wavelets' : 'ecw',
792
'GRIdded Binary (.grb)' : 'grb',
793
'EUMETSAT Archive native (.nat)' : 'nat',
794
'Idrisi Raster A.1' : 'rst',
795
'Golden Software ASCII Grid (.grd)' : 'grd',
796
'Golden Software Binary Grid (.grd)' : 'grd',
797
'Golden Software 7 Binary Grid (.grd)' : 'grd',
798
'R Object Data Store' : 'r',
799
'USGS DOQ (Old Style)' : 'doq',
800
'USGS DOQ (New Style)' : 'doq',
801
'ENVI .hdr Labelled' : 'hdr',
802
'ESRI .hdr Labelled' : 'hdr',
803
'Generic Binary (.hdr Labelled)' : 'hdr',
804
'PCI .aux Labelled' : 'aux',
805
'EOSAT FAST Format' : 'fst',
806
'VTP .bt (Binary Terrain) 1.3 Format' : 'bt',
807
'FARSITE v.4 Landscape File (.lcp)' : 'lcp',
808
'Swedish Grid RIK (.rik)' : 'rik',
809
'USGS Optional ASCII DEM (and CDED)' : 'dem',
810
'Northwood Numeric Grid Format .grd/.tab' : '',
811
'Northwood Classified Grid Format .grc/.tab' : '',
812
'ARC Digitized Raster Graphics' : 'arc',
813
'Magellan topo (.blx)' : 'blx',
814
'SAGA GIS Binary Grid (.sdat)' : 'sdat'
818
vectorFormatExtension = {
819
'ESRI Shapefile' : 'shp',
836
'Geoconcept' : 'gxt',
838
'GPSTrackMaker' : 'gtm',
844
def GetSettingsPath():
845
"""Get full path to the settings directory
848
verFd = open(os.path.join(globalvar.ETCDIR, "VERSIONNUMBER"))
849
version = int(verFd.readlines()[0].split(' ')[0].split('.')[0])
850
except (IOError, ValueError, TypeError, IndexError) as e:
851
sys.exit(_("ERROR: Unable to determine GRASS version. Details: %s") % e)
855
# keep location of settings files rc and wx in sync with lib/init/grass.py
856
if sys.platform == 'win32':
857
return os.path.join(os.getenv('APPDATA'), 'GRASS%d' % version)
859
return os.path.join(os.getenv('HOME'), '.grass%d' % version)
861
def StoreEnvVariable(key, value = None, envFile = None):
862
"""Store environmental variable
864
If value is not given (is None) then environmental variable is
868
:param value: env value
869
:param envFile: path to the environmental file (None for default location)
871
windows = sys.platform == 'win32'
873
gVersion = grass.version()['version'].split('.', 1)[0]
875
envFile = os.path.join(os.getenv('HOME'), '.grass%s' % gVersion, 'bashrc')
877
envFile = os.path.join(os.getenv('APPDATA'), 'GRASS%s' % gVersion, 'env.bat')
882
if os.path.exists(envFile):
886
sys.stderr.write(_("Unable to open file '%s'\n") % envFile)
888
for line in fd.readlines():
889
line = line.rstrip(os.linesep)
891
k, v = map(lambda x: x.strip(), line.split(' ', 1)[1].split('=', 1))
892
except StandardError as e:
893
sys.stderr.write(_("%s: line skipped - unable to parse '%s'\n"
894
"Reason: %s\n") % (envFile, line, e))
895
lineSkipped.append(line)
898
sys.stderr.write(_("Duplicated key: %s\n") % k)
903
# update environmental variables
910
# write update env file
912
fd = open(envFile, 'w')
914
sys.stderr.write(_("Unable to create file '%s'\n") % envFile)
921
for key, value in environ.iteritems():
922
fd.write('%s %s=%s\n' % (expCmd, key, value))
924
# write also skipped lines
925
for line in lineSkipped:
926
fd.write(line + os.linesep)
930
def SetAddOnPath(addonPath = None, key = 'PATH'):
931
"""Set default AddOn path
933
:param addonPath: path to addons (None for default)
934
:param key: env key - 'PATH' or 'BASE'
936
gVersion = grass.version()['version'].split('.', 1)[0]
939
if sys.platform != 'win32':
940
addonPath = os.path.join(os.path.join(os.getenv('HOME'),
941
'.grass%s' % gVersion,
944
addonPath = os.path.join(os.path.join(os.getenv('APPDATA'),
945
'GRASS%s' % gVersion,
948
StoreEnvVariable(key = 'GRASS_ADDON_' + key, value = addonPath)
949
os.environ['GRASS_ADDON_' + key] = addonPath
952
if addonPath not in os.environ['PATH']:
953
os.environ['PATH'] = addonPath + os.pathsep + os.environ['PATH']
956
# predefined colors and their names
957
# must be in sync with lib/gis/color_str.c
958
str2rgb = {'aqua': (100, 128, 255),
961
'brown': (180, 77, 25),
962
'cyan': (0, 255, 255),
963
'gray': (128, 128, 128),
964
'grey': (128, 128, 128),
965
'green': (0, 255, 0),
966
'indigo': (0, 128, 255),
967
'magenta': (255, 0, 255),
968
'orange': (255, 128, 0),
970
'violet': (128, 0, 255),
971
'purple': (128, 0, 255),
972
'white': (255, 255, 255),
973
'yellow': (255, 255, 0)}
975
for (s, r) in str2rgb.items():
977
# ensure that gray value has 'gray' string and not 'grey'
978
rgb2str[str2rgb['gray']] = 'gray'
979
# purple is defined as nickname for violet in lib/gis
980
# (although Wikipedia says that purple is (128, 0, 128))
981
# we will prefer the defined color, not nickname
982
rgb2str[str2rgb['violet']] = 'violet'
985
def color_resolve(color):
986
if len(color) > 0 and color[0] in "0123456789":
987
rgb = tuple(map(int, color.split(':')))
990
# Convert color names to RGB
995
rgb = (200, 200, 200)
996
label = _('Select Color')
999
command2ltype = {'d.rast' : 'raster',
1000
'd.rast3d' : '3d-raster',
1003
'd.shade' : 'shaded',
1004
'd.legend' : 'rastleg',
1005
'd.rast.arrow' : 'rastarrow',
1006
'd.rast.num' : 'rastnum',
1007
'd.rast.leg' : 'maplegend',
1008
'd.vect' : 'vector',
1009
'd.vect.thematic': 'thememap',
1010
'd.vect.chart' : 'themechart',
1012
'd.geodesic' : 'geodesic',
1013
'd.rhumbline' : 'rhumb',
1014
'd.labels' : 'labels',
1015
'd.barscale' : 'barscale',
1016
'd.redraw' : 'redraw',
1018
'd.histogram' : 'histogram',
1019
'd.colortable' : 'colortable',
1020
'd.graph' : 'graph',
1021
'd.out.file' : 'export',
1022
'd.to.rast' : 'torast',
1026
for (cmd, ltype) in command2ltype.items():
1027
ltype2command[ltype] = cmd
1030
def GetGEventAttribsForHandler(method, event):
1031
"""Get attributes from event, which can be used by handler method.
1033
Be aware of event class attributes.
1035
:param method: handler method (including self arg)
1038
:return: (valid kwargs for method,
1039
list of method's args without default value
1040
which were not found among event attributes)
1042
args_spec = inspect.getargspec(method)
1048
defaults = args_spec[3]
1050
# number of arguments without def value
1051
req_args = len(args) - 1 - len(defaults)
1056
for i, a in enumerate(args):
1057
if hasattr(event, a):
1058
kwargs[a] = getattr(event, a)
1060
missing_args.append(a)
1062
return kwargs, missing_args
1064
def GuiModuleMain(mainfn):
1065
"""Main function for g.gui.* modules
1067
os.fork removed in r62649 as fragile
1072
def PilImageToWxImage(pilImage, copyAlpha = True):
1073
"""Convert PIL image to wx.Image
1075
Based on http://wiki.wxpython.org/WorkingWithImages
1078
hasAlpha = pilImage.mode[-1] == 'A'
1079
if copyAlpha and hasAlpha : # Make sure there is an alpha layer copy.
1080
wxImage = wx.EmptyImage(*pilImage.size)
1081
pilImageCopyRGBA = pilImage.copy()
1082
pilImageCopyRGB = pilImageCopyRGBA.convert('RGB') # RGBA --> RGB
1083
pilImageRgbData = pilImageCopyRGB.tostring()
1084
wxImage.SetData(pilImageRgbData)
1085
wxImage.SetAlphaData(pilImageCopyRGBA.tostring()[3::4]) # Create layer and insert alpha values.
1087
else : # The resulting image will not have alpha.
1088
wxImage = wx.EmptyImage(*pilImage.size)
1089
pilImageCopy = pilImage.copy()
1090
pilImageCopyRGB = pilImageCopy.convert('RGB') # Discard any alpha from the PIL image.
1091
pilImageRgbData = pilImageCopyRGB.tostring()
1092
wxImage.SetData(pilImageRgbData)
1097
def autoCropImageFromFile(filename):
1098
"""Loads image from file and crops it automatically.
1100
If PIL is not installed, it does not crop it.
1102
:param filename: path to file
1103
:return: wx.Image instance
1106
from PIL import Image
1107
pilImage = Image.open(filename)
1108
imageBox = pilImage.getbbox()
1109
cropped = pilImage.crop(imageBox)
1110
return PilImageToWxImage(cropped, copyAlpha=True)
1113
return wx.Image(filename)
1116
def isInRegion(regionA, regionB):
1117
"""Tests if 'regionA' is inside of 'regionB'.
1119
For example, region A is a display region and region B is some reference
1120
region, e.g., a computational region.
1122
>>> displayRegion = {'n': 223900, 's': 217190, 'w': 630780, 'e': 640690}
1123
>>> compRegion = {'n': 228500, 's': 215000, 'w': 630000, 'e': 645000}
1124
>>> isInRegion(displayRegion, compRegion)
1126
>>> displayRegion = {'n':226020, 's': 212610, 'w': 626510, 'e': 646330}
1127
>>> isInRegion(displayRegion, compRegion)
1130
:param regionA: input region A as dictionary
1131
:param regionB: input region B as dictionary
1133
:return: True if region A is inside of region B
1134
:return: False othewise
1136
if regionA['s'] >= regionB['s'] and \
1137
regionA['n'] <= regionB['n'] and \
1138
regionA['w'] >= regionB['w'] and \
1139
regionA['e'] <= regionB['e']:
1145
def do_doctest_gettext_workaround():
1146
"""Setups environment for doing a doctest with gettext usage.
1148
When using gettext with dynamically defined underscore function
1149
(`_("For translation")`), doctest does not work properly. One option is to
1150
use `import as` instead of dynamically defined underscore function but this
1151
would require change all modules which are used by tested module. This
1152
should be considered for the future. The second option is to define dummy
1153
underscore function and one other function which creates the right
1154
environment to satisfy all. This is done by this function.
1156
def new_displayhook(string):
1157
"""A replacement for default `sys.displayhook`"""
1158
if string is not None:
1159
sys.stdout.write("%r\n" % (string,))
1161
def new_translator(string):
1162
"""A fake gettext underscore function."""
1165
sys.displayhook = new_displayhook
1168
__builtin__._ = new_translator
1172
"""Tests the module using doctest
1174
:return: a number of failed tests
1177
do_doctest_gettext_workaround()
1178
return doctest.testmod().failed
1181
if __name__ == '__main__':
1182
sys.exit(doc_test())