| 1 | """
|
|---|
| 2 | @package core.utils
|
|---|
| 3 |
|
|---|
| 4 | @brief Misc utilities for wxGUI
|
|---|
| 5 |
|
|---|
| 6 | (C) 2007-2015 by the GRASS Development Team
|
|---|
| 7 |
|
|---|
| 8 | This program is free software under the GNU General Public License
|
|---|
| 9 | (>=v2). Read the file COPYING that comes with GRASS for details.
|
|---|
| 10 |
|
|---|
| 11 | @author Martin Landa <landa.martin gmail.com>
|
|---|
| 12 | @author Jachym Cepicky
|
|---|
| 13 | """
|
|---|
| 14 |
|
|---|
| 15 | import os
|
|---|
| 16 | import sys
|
|---|
| 17 | import platform
|
|---|
| 18 | import string
|
|---|
| 19 | import glob
|
|---|
| 20 | import shlex
|
|---|
| 21 | import re
|
|---|
| 22 | import inspect
|
|---|
| 23 | import six
|
|---|
| 24 |
|
|---|
| 25 | from grass.script import core as grass
|
|---|
| 26 | from grass.script import task as gtask
|
|---|
| 27 | from grass.exceptions import OpenError
|
|---|
| 28 |
|
|---|
| 29 | from core.gcmd import RunCommand
|
|---|
| 30 | from core.debug import Debug
|
|---|
| 31 | from core.globalvar import ETCDIR, wxPythonPhoenix
|
|---|
| 32 |
|
|---|
| 33 | def cmp(a, b):
|
|---|
| 34 | """cmp function"""
|
|---|
| 35 | return ((a > b) - (a < b))
|
|---|
| 36 |
|
|---|
| 37 |
|
|---|
| 38 | def normalize_whitespace(text):
|
|---|
| 39 | """Remove redundant whitespace from a string"""
|
|---|
| 40 | return (' ').join(text.split())
|
|---|
| 41 |
|
|---|
| 42 |
|
|---|
| 43 | def split(s):
|
|---|
| 44 | """Platform spefic shlex.split"""
|
|---|
| 45 | try:
|
|---|
| 46 | if sys.platform == "win32":
|
|---|
| 47 | return shlex.split(s.replace('\\', r'\\'))
|
|---|
| 48 | else:
|
|---|
| 49 | return shlex.split(s)
|
|---|
| 50 | except ValueError as e:
|
|---|
| 51 | sys.stderr.write(_("Syntax error: %s") % e)
|
|---|
| 52 |
|
|---|
| 53 | return []
|
|---|
| 54 |
|
|---|
| 55 |
|
|---|
| 56 | def GetTempfile(pref=None):
|
|---|
| 57 | """Creates GRASS temporary file using defined prefix.
|
|---|
| 58 |
|
|---|
| 59 | .. todo::
|
|---|
| 60 | Fix path on MS Windows/MSYS
|
|---|
| 61 |
|
|---|
| 62 | :param pref: prefer the given path
|
|---|
| 63 |
|
|---|
| 64 | :return: Path to file name (string) or None
|
|---|
| 65 | """
|
|---|
| 66 | ret = RunCommand('g.tempfile',
|
|---|
| 67 | read=True,
|
|---|
| 68 | pid=os.getpid())
|
|---|
| 69 |
|
|---|
| 70 | tempfile = ret.splitlines()[0].strip()
|
|---|
| 71 |
|
|---|
| 72 | # FIXME
|
|---|
| 73 | # ugly hack for MSYS (MS Windows)
|
|---|
| 74 | if platform.system() == 'Windows':
|
|---|
| 75 | tempfile = tempfile.replace("/", "\\")
|
|---|
| 76 | try:
|
|---|
| 77 | path, file = os.path.split(tempfile)
|
|---|
| 78 | if pref:
|
|---|
| 79 | return os.path.join(pref, file)
|
|---|
| 80 | else:
|
|---|
| 81 | return tempfile
|
|---|
| 82 | except:
|
|---|
| 83 | return None
|
|---|
| 84 |
|
|---|
| 85 |
|
|---|
| 86 | def GetLayerNameFromCmd(dcmd, fullyQualified=False, param=None,
|
|---|
| 87 | layerType=None):
|
|---|
| 88 | """Get map name from GRASS command
|
|---|
| 89 |
|
|---|
| 90 | Parameter dcmd can be modified when first parameter is not
|
|---|
| 91 | defined.
|
|---|
| 92 |
|
|---|
| 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',
|
|---|
| 97 | 'raster_3d', ...)
|
|---|
| 98 |
|
|---|
| 99 | :return: tuple (name, found)
|
|---|
| 100 | """
|
|---|
| 101 | mapname = ''
|
|---|
| 102 | found = True
|
|---|
| 103 |
|
|---|
| 104 | if len(dcmd) < 1:
|
|---|
| 105 | return mapname, False
|
|---|
| 106 |
|
|---|
| 107 | if 'd.grid' == dcmd[0]:
|
|---|
| 108 | mapname = 'grid'
|
|---|
| 109 | elif 'd.geodesic' in dcmd[0]:
|
|---|
| 110 | mapname = 'geodesic'
|
|---|
| 111 | elif 'd.rhumbline' in dcmd[0]:
|
|---|
| 112 | mapname = 'rhumb'
|
|---|
| 113 | elif 'd.graph' in dcmd[0]:
|
|---|
| 114 | mapname = 'graph'
|
|---|
| 115 | else:
|
|---|
| 116 | params = list()
|
|---|
| 117 | for idx in range(len(dcmd)):
|
|---|
| 118 | try:
|
|---|
| 119 | p, v = dcmd[idx].split('=', 1)
|
|---|
| 120 | except ValueError:
|
|---|
| 121 | continue
|
|---|
| 122 |
|
|---|
| 123 | if p == param:
|
|---|
| 124 | params = [(idx, p, v)]
|
|---|
| 125 | break
|
|---|
| 126 |
|
|---|
| 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',
|
|---|
| 131 | 'shade', 'labels'):
|
|---|
| 132 | params.append((idx, p, v))
|
|---|
| 133 |
|
|---|
| 134 | if len(params) < 1:
|
|---|
| 135 | if len(dcmd) > 1:
|
|---|
| 136 | i = 1
|
|---|
| 137 | while i < len(dcmd):
|
|---|
| 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]))
|
|---|
| 143 | break
|
|---|
| 144 | i += 1
|
|---|
| 145 | else:
|
|---|
| 146 | return mapname, False
|
|---|
| 147 |
|
|---|
| 148 | if len(params) < 1:
|
|---|
| 149 | return mapname, False
|
|---|
| 150 |
|
|---|
| 151 | # need to add mapset for all maps
|
|---|
| 152 | mapsets = {}
|
|---|
| 153 | for i, p, v in params:
|
|---|
| 154 | if p == 'layer':
|
|---|
| 155 | continue
|
|---|
| 156 | mapname = v
|
|---|
| 157 | mapset = ''
|
|---|
| 158 | if fullyQualified and '@' not in mapname:
|
|---|
| 159 | if layerType in ('raster', 'vector',
|
|---|
| 160 | 'raster_3d', 'rgb', 'his'):
|
|---|
| 161 | try:
|
|---|
| 162 | if layerType in ('raster', 'rgb', 'his'):
|
|---|
| 163 | findType = 'cell'
|
|---|
| 164 | elif layerType == 'raster_3d':
|
|---|
| 165 | findType = 'grid3'
|
|---|
| 166 | else:
|
|---|
| 167 | findType = layerType
|
|---|
| 168 | mapset = grass.find_file(
|
|---|
| 169 | mapname, element=findType)['mapset']
|
|---|
| 170 | except AttributeError: # not found
|
|---|
| 171 | return '', False
|
|---|
| 172 | if not mapset:
|
|---|
| 173 | found = False
|
|---|
| 174 | else:
|
|---|
| 175 | mapset = '' # grass.gisenv()['MAPSET']
|
|---|
| 176 | mapsets[i] = mapset
|
|---|
| 177 |
|
|---|
| 178 | # update dcmd
|
|---|
| 179 | for i, p, v in params:
|
|---|
| 180 | if p == 'layer':
|
|---|
| 181 | continue
|
|---|
| 182 | dcmd[i] = p + '=' + v
|
|---|
| 183 | if i in mapsets and mapsets[i]:
|
|---|
| 184 | dcmd[i] += '@' + mapsets[i]
|
|---|
| 185 |
|
|---|
| 186 | maps = list()
|
|---|
| 187 | ogr = False
|
|---|
| 188 | for i, p, v in params:
|
|---|
| 189 | if v.lower().rfind('@ogr') > -1:
|
|---|
| 190 | ogr = True
|
|---|
| 191 | if p == 'layer' and not ogr:
|
|---|
| 192 | continue
|
|---|
| 193 | maps.append(dcmd[i].split('=', 1)[1])
|
|---|
| 194 |
|
|---|
| 195 | mapname = '\n'.join(maps)
|
|---|
| 196 |
|
|---|
| 197 | return mapname, found
|
|---|
| 198 |
|
|---|
| 199 |
|
|---|
| 200 | def GetValidLayerName(name):
|
|---|
| 201 | """Make layer name SQL compliant, based on G_str_to_sql()
|
|---|
| 202 |
|
|---|
| 203 | .. todo::
|
|---|
| 204 | Better use directly Ctypes to reuse venerable libgis C fns...
|
|---|
| 205 | """
|
|---|
| 206 | retName = name.strip()
|
|---|
| 207 |
|
|---|
| 208 | # check if name is fully qualified
|
|---|
| 209 | if '@' in retName:
|
|---|
| 210 | retName, mapset = retName.split('@')
|
|---|
| 211 | else:
|
|---|
| 212 | mapset = None
|
|---|
| 213 |
|
|---|
| 214 | cIdx = 0
|
|---|
| 215 | retNameList = list(retName)
|
|---|
| 216 | for c in retNameList:
|
|---|
| 217 | if not (c >= 'A' and c <= 'Z') and \
|
|---|
| 218 | not (c >= 'a' and c <= 'z') and \
|
|---|
| 219 | not (c >= '0' and c <= '9'):
|
|---|
| 220 | retNameList[cIdx] = '_'
|
|---|
| 221 | cIdx += 1
|
|---|
| 222 | retName = ''.join(retNameList)
|
|---|
| 223 |
|
|---|
| 224 | if not (retName[0] >= 'A' and retName[0] <= 'Z') and \
|
|---|
| 225 | not (retName[0] >= 'a' and retName[0] <= 'z'):
|
|---|
| 226 | retName = 'x' + retName[1:]
|
|---|
| 227 |
|
|---|
| 228 | if mapset:
|
|---|
| 229 | retName = retName + '@' + mapset
|
|---|
| 230 |
|
|---|
| 231 | return retName
|
|---|
| 232 |
|
|---|
| 233 |
|
|---|
| 234 | def ListOfCatsToRange(cats):
|
|---|
| 235 | """Convert list of category number to range(s)
|
|---|
| 236 |
|
|---|
| 237 | Used for example for d.vect cats=[range]
|
|---|
| 238 |
|
|---|
| 239 | :param cats: category list
|
|---|
| 240 |
|
|---|
| 241 | :return: category range string
|
|---|
| 242 | :return: '' on error
|
|---|
| 243 | """
|
|---|
| 244 |
|
|---|
| 245 | catstr = ''
|
|---|
| 246 |
|
|---|
| 247 | try:
|
|---|
| 248 | cats = list(map(int, cats))
|
|---|
| 249 | except:
|
|---|
| 250 | return catstr
|
|---|
| 251 |
|
|---|
| 252 | i = 0
|
|---|
| 253 | while i < len(cats):
|
|---|
| 254 | next = 0
|
|---|
| 255 | j = i + 1
|
|---|
| 256 | while j < len(cats):
|
|---|
| 257 | if cats[i + next] == cats[j] - 1:
|
|---|
| 258 | next += 1
|
|---|
| 259 | else:
|
|---|
| 260 | break
|
|---|
| 261 | j += 1
|
|---|
| 262 |
|
|---|
| 263 | if next > 1:
|
|---|
| 264 | catstr += '%d-%d,' % (cats[i], cats[i + next])
|
|---|
| 265 | i += next + 1
|
|---|
| 266 | else:
|
|---|
| 267 | catstr += '%d,' % (cats[i])
|
|---|
| 268 | i += 1
|
|---|
| 269 |
|
|---|
| 270 | return catstr.strip(',')
|
|---|
| 271 |
|
|---|
| 272 |
|
|---|
| 273 | def ListOfMapsets(get='ordered'):
|
|---|
| 274 | """Get list of available/accessible mapsets
|
|---|
| 275 |
|
|---|
| 276 | :param str get: method ('all', 'accessible', 'ordered')
|
|---|
| 277 |
|
|---|
| 278 | :return: list of mapsets
|
|---|
| 279 | :return: None on error
|
|---|
| 280 | """
|
|---|
| 281 | mapsets = []
|
|---|
| 282 |
|
|---|
| 283 | if get == 'all' or get == 'ordered':
|
|---|
| 284 | ret = RunCommand('g.mapsets',
|
|---|
| 285 | read=True,
|
|---|
| 286 | quiet=True,
|
|---|
| 287 | flags='l',
|
|---|
| 288 | sep='newline')
|
|---|
| 289 |
|
|---|
| 290 | if ret:
|
|---|
| 291 | mapsets = ret.splitlines()
|
|---|
| 292 | ListSortLower(mapsets)
|
|---|
| 293 | else:
|
|---|
| 294 | return None
|
|---|
| 295 |
|
|---|
| 296 | if get == 'accessible' or get == 'ordered':
|
|---|
| 297 | ret = RunCommand('g.mapsets',
|
|---|
| 298 | read=True,
|
|---|
| 299 | quiet=True,
|
|---|
| 300 | flags='p',
|
|---|
| 301 | sep='newline')
|
|---|
| 302 | if ret:
|
|---|
| 303 | if get == 'accessible':
|
|---|
| 304 | mapsets = ret.splitlines()
|
|---|
| 305 | else:
|
|---|
| 306 | mapsets_accessible = ret.splitlines()
|
|---|
| 307 | for mapset in mapsets_accessible:
|
|---|
| 308 | mapsets.remove(mapset)
|
|---|
| 309 | mapsets = mapsets_accessible + mapsets
|
|---|
| 310 | else:
|
|---|
| 311 | return None
|
|---|
| 312 |
|
|---|
| 313 | return mapsets
|
|---|
| 314 |
|
|---|
| 315 |
|
|---|
| 316 | def ListSortLower(list):
|
|---|
| 317 | """Sort list items (not case-sensitive)"""
|
|---|
| 318 | list.sort(key=lambda x: x.lower())
|
|---|
| 319 |
|
|---|
| 320 |
|
|---|
| 321 | def GetVectorNumberOfLayers(vector):
|
|---|
| 322 | """Get list of all vector layers"""
|
|---|
| 323 | layers = list()
|
|---|
| 324 | if not vector:
|
|---|
| 325 | return layers
|
|---|
| 326 |
|
|---|
| 327 | fullname = grass.find_file(name=vector, element='vector')['fullname']
|
|---|
| 328 | if not fullname:
|
|---|
| 329 | Debug.msg(
|
|---|
| 330 | 5,
|
|---|
| 331 | "utils.GetVectorNumberOfLayers(): vector map '%s' not found" %
|
|---|
| 332 | vector)
|
|---|
| 333 | return layers
|
|---|
| 334 |
|
|---|
| 335 | ret, out, msg = RunCommand('v.category',
|
|---|
| 336 | getErrorMsg=True,
|
|---|
| 337 | read=True,
|
|---|
| 338 | input=fullname,
|
|---|
| 339 | option='layers')
|
|---|
| 340 | if ret != 0:
|
|---|
| 341 | sys.stderr.write(
|
|---|
| 342 | _("Vector map <%(map)s>: %(msg)s\n") %
|
|---|
| 343 | {'map': fullname, 'msg': msg})
|
|---|
| 344 | return layers
|
|---|
| 345 | else:
|
|---|
| 346 | Debug.msg(1, "GetVectorNumberOfLayers(): ret %s" % ret)
|
|---|
| 347 |
|
|---|
| 348 | for layer in out.splitlines():
|
|---|
| 349 | layers.append(layer)
|
|---|
| 350 |
|
|---|
| 351 | Debug.msg(3, "utils.GetVectorNumberOfLayers(): vector=%s -> %s" %
|
|---|
| 352 | (fullname, ','.join(layers)))
|
|---|
| 353 |
|
|---|
| 354 | return layers
|
|---|
| 355 |
|
|---|
| 356 |
|
|---|
| 357 | def Deg2DMS(lon, lat, string=True, hemisphere=True, precision=3):
|
|---|
| 358 | """Convert deg value to dms string
|
|---|
| 359 |
|
|---|
| 360 | :param lon: longitude (x)
|
|---|
| 361 | :param lat: latitude (y)
|
|---|
| 362 | :param string: True to return string otherwise tuple
|
|---|
| 363 | :param hemisphere: print hemisphere
|
|---|
| 364 | :param precision: seconds precision
|
|---|
| 365 |
|
|---|
| 366 | :return: DMS string or tuple of values
|
|---|
| 367 | :return: empty string on error
|
|---|
| 368 | """
|
|---|
| 369 | try:
|
|---|
| 370 | flat = float(lat)
|
|---|
| 371 | flon = float(lon)
|
|---|
| 372 | except ValueError:
|
|---|
| 373 | if string:
|
|---|
| 374 | return ''
|
|---|
| 375 | else:
|
|---|
| 376 | return None
|
|---|
| 377 |
|
|---|
| 378 | # fix longitude
|
|---|
| 379 | while flon > 180.0:
|
|---|
| 380 | flon -= 360.0
|
|---|
| 381 | while flon < -180.0:
|
|---|
| 382 | flon += 360.0
|
|---|
| 383 |
|
|---|
| 384 | # hemisphere
|
|---|
| 385 | if hemisphere:
|
|---|
| 386 | if flat < 0.0:
|
|---|
| 387 | flat = abs(flat)
|
|---|
| 388 | hlat = 'S'
|
|---|
| 389 | else:
|
|---|
| 390 | hlat = 'N'
|
|---|
| 391 |
|
|---|
| 392 | if flon < 0.0:
|
|---|
| 393 | hlon = 'W'
|
|---|
| 394 | flon = abs(flon)
|
|---|
| 395 | else:
|
|---|
| 396 | hlon = 'E'
|
|---|
| 397 | else:
|
|---|
| 398 | flat = abs(flat)
|
|---|
| 399 | flon = abs(flon)
|
|---|
| 400 | hlon = ''
|
|---|
| 401 | hlat = ''
|
|---|
| 402 |
|
|---|
| 403 | slat = __ll_parts(flat, precision=precision)
|
|---|
| 404 | slon = __ll_parts(flon, precision=precision)
|
|---|
| 405 |
|
|---|
| 406 | if string:
|
|---|
| 407 | return slon + hlon + '; ' + slat + hlat
|
|---|
| 408 |
|
|---|
| 409 | return (slon + hlon, slat + hlat)
|
|---|
| 410 |
|
|---|
| 411 |
|
|---|
| 412 | def DMS2Deg(lon, lat):
|
|---|
| 413 | """Convert dms value to deg
|
|---|
| 414 |
|
|---|
| 415 | :param lon: longitude (x)
|
|---|
| 416 | :param lat: latitude (y)
|
|---|
| 417 |
|
|---|
| 418 | :return: tuple of converted values
|
|---|
| 419 | :return: ValueError on error
|
|---|
| 420 | """
|
|---|
| 421 | x = __ll_parts(lon, reverse=True)
|
|---|
| 422 | y = __ll_parts(lat, reverse=True)
|
|---|
| 423 |
|
|---|
| 424 | return (x, y)
|
|---|
| 425 |
|
|---|
| 426 |
|
|---|
| 427 | def __ll_parts(value, reverse=False, precision=3):
|
|---|
| 428 | """Converts deg to d:m:s string
|
|---|
| 429 |
|
|---|
| 430 | :param value: value to be converted
|
|---|
| 431 | :param reverse: True to convert from d:m:s to deg
|
|---|
| 432 | :param precision: seconds precision (ignored if reverse is True)
|
|---|
| 433 |
|
|---|
| 434 | :return: converted value (string/float)
|
|---|
| 435 | :return: ValueError on error (reverse == True)
|
|---|
| 436 | """
|
|---|
| 437 | if not reverse:
|
|---|
| 438 | if value == 0.0:
|
|---|
| 439 | return '%s%.*f' % ('00:00:0', precision, 0.0)
|
|---|
| 440 |
|
|---|
| 441 | d = int(int(value))
|
|---|
| 442 | m = int((value - d) * 60)
|
|---|
| 443 | s = ((value - d) * 60 - m) * 60
|
|---|
| 444 | if m < 0:
|
|---|
| 445 | m = '00'
|
|---|
| 446 | elif m < 10:
|
|---|
| 447 | m = '0' + str(m)
|
|---|
| 448 | else:
|
|---|
| 449 | m = str(m)
|
|---|
| 450 | if s < 0:
|
|---|
| 451 | s = '00.0000'
|
|---|
| 452 | elif s < 10.0:
|
|---|
| 453 | s = '0%.*f' % (precision, s)
|
|---|
| 454 | else:
|
|---|
| 455 | s = '%.*f' % (precision, s)
|
|---|
| 456 |
|
|---|
| 457 | return str(d) + ':' + m + ':' + s
|
|---|
| 458 | else: # -> reverse
|
|---|
| 459 | try:
|
|---|
| 460 | d, m, s = value.split(':')
|
|---|
| 461 | hs = s[-1]
|
|---|
| 462 | s = s[:-1]
|
|---|
| 463 | except ValueError:
|
|---|
| 464 | try:
|
|---|
| 465 | d, m = value.split(':')
|
|---|
| 466 | hs = m[-1]
|
|---|
| 467 | m = m[:-1]
|
|---|
| 468 | s = '0.0'
|
|---|
| 469 | except ValueError:
|
|---|
| 470 | try:
|
|---|
| 471 | d = value
|
|---|
| 472 | hs = d[-1]
|
|---|
| 473 | d = d[:-1]
|
|---|
| 474 | m = '0'
|
|---|
| 475 | s = '0.0'
|
|---|
| 476 | except ValueError:
|
|---|
| 477 | raise ValueError
|
|---|
| 478 |
|
|---|
| 479 | if hs not in ('N', 'S', 'E', 'W'):
|
|---|
| 480 | raise ValueError
|
|---|
| 481 |
|
|---|
| 482 | coef = 1.0
|
|---|
| 483 | if hs in ('S', 'W'):
|
|---|
| 484 | coef = -1.0
|
|---|
| 485 |
|
|---|
| 486 | fm = int(m) / 60.0
|
|---|
| 487 | fs = float(s) / (60 * 60)
|
|---|
| 488 |
|
|---|
| 489 | return coef * (float(d) + fm + fs)
|
|---|
| 490 |
|
|---|
| 491 |
|
|---|
| 492 | def GetCmdString(cmd):
|
|---|
| 493 | """Get GRASS command as string.
|
|---|
| 494 |
|
|---|
| 495 | :param cmd: GRASS command given as tuple
|
|---|
| 496 |
|
|---|
| 497 | :return: command string
|
|---|
| 498 | """
|
|---|
| 499 | return ' '.join(gtask.cmdtuple_to_list(cmd))
|
|---|
| 500 |
|
|---|
| 501 |
|
|---|
| 502 | def PathJoin(*args):
|
|---|
| 503 | """Check path created by os.path.join"""
|
|---|
| 504 | path = os.path.join(*args)
|
|---|
| 505 | if platform.system() == 'Windows' and \
|
|---|
| 506 | '/' in path:
|
|---|
| 507 | return path[1].upper() + ':\\' + path[3:].replace('/', '\\')
|
|---|
| 508 |
|
|---|
| 509 | return path
|
|---|
| 510 |
|
|---|
| 511 |
|
|---|
| 512 | def ReadEpsgCodes():
|
|---|
| 513 | """Read EPSG codes with g.proj
|
|---|
| 514 |
|
|---|
| 515 | :return: dictionary of EPSG code
|
|---|
| 516 | """
|
|---|
| 517 | epsgCodeDict = dict()
|
|---|
| 518 |
|
|---|
| 519 | ret = RunCommand('g.proj',
|
|---|
| 520 | read=True,
|
|---|
| 521 | list_codes="EPSG")
|
|---|
| 522 |
|
|---|
| 523 | for line in ret.splitlines():
|
|---|
| 524 | code, descr, params = line.split("|")
|
|---|
| 525 | epsgCodeDict[int(code)] = (descr, params)
|
|---|
| 526 |
|
|---|
| 527 | return epsgCodeDict
|
|---|
| 528 |
|
|---|
| 529 |
|
|---|
| 530 | def ReprojectCoordinates(coord, projOut, projIn=None, flags=''):
|
|---|
| 531 | """Reproject coordinates
|
|---|
| 532 |
|
|---|
| 533 | :param coord: coordinates given as tuple
|
|---|
| 534 | :param projOut: output projection
|
|---|
| 535 | :param projIn: input projection (use location projection settings)
|
|---|
| 536 |
|
|---|
| 537 | :return: reprojected coordinates (returned as tuple)
|
|---|
| 538 | """
|
|---|
| 539 | coors = RunCommand('m.proj',
|
|---|
| 540 | flags=flags,
|
|---|
| 541 | input='-',
|
|---|
| 542 | proj_in=projIn,
|
|---|
| 543 | proj_out=projOut,
|
|---|
| 544 | sep=';',
|
|---|
| 545 | stdin='%f;%f' % (coord[0], coord[1]),
|
|---|
| 546 | read=True)
|
|---|
| 547 | if coors:
|
|---|
| 548 | coors = coors.split(';')
|
|---|
| 549 | e = coors[0]
|
|---|
| 550 | n = coors[1]
|
|---|
| 551 | try:
|
|---|
| 552 | proj = projOut.split(' ')[0].split('=')[1]
|
|---|
| 553 | except IndexError:
|
|---|
| 554 | proj = ''
|
|---|
| 555 | if proj in ('ll', 'latlong', 'longlat') and 'd' not in flags:
|
|---|
| 556 | return (proj, (e, n))
|
|---|
| 557 | else:
|
|---|
| 558 | try:
|
|---|
| 559 | return (proj, (float(e), float(n)))
|
|---|
| 560 | except ValueError:
|
|---|
| 561 | return (None, None)
|
|---|
| 562 |
|
|---|
| 563 | return (None, None)
|
|---|
| 564 |
|
|---|
| 565 |
|
|---|
| 566 | def GetListOfLocations(dbase):
|
|---|
| 567 | """Get list of GRASS locations in given dbase
|
|---|
| 568 |
|
|---|
| 569 | :param dbase: GRASS database path
|
|---|
| 570 |
|
|---|
| 571 | :return: list of locations (sorted)
|
|---|
| 572 | """
|
|---|
| 573 | listOfLocations = list()
|
|---|
| 574 |
|
|---|
| 575 | try:
|
|---|
| 576 | for location in glob.glob(os.path.join(dbase, "*")):
|
|---|
| 577 | try:
|
|---|
| 578 | if os.path.join(
|
|---|
| 579 | location, "PERMANENT") in glob.glob(
|
|---|
| 580 | os.path.join(location, "*")):
|
|---|
| 581 | listOfLocations.append(os.path.basename(location))
|
|---|
| 582 | except:
|
|---|
| 583 | pass
|
|---|
| 584 | except (UnicodeEncodeError, UnicodeDecodeError) as e:
|
|---|
| 585 | raise e
|
|---|
| 586 |
|
|---|
| 587 | ListSortLower(listOfLocations)
|
|---|
| 588 |
|
|---|
| 589 | return listOfLocations
|
|---|
| 590 |
|
|---|
| 591 |
|
|---|
| 592 | def GetListOfMapsets(dbase, location, selectable=False):
|
|---|
| 593 | """Get list of mapsets in given GRASS location
|
|---|
| 594 |
|
|---|
| 595 | :param dbase: GRASS database path
|
|---|
| 596 | :param location: GRASS location
|
|---|
| 597 | :param selectable: True to get list of selectable mapsets, otherwise all
|
|---|
| 598 |
|
|---|
| 599 | :return: list of mapsets - sorted (PERMANENT first)
|
|---|
| 600 | """
|
|---|
| 601 | listOfMapsets = list()
|
|---|
| 602 |
|
|---|
| 603 | if selectable:
|
|---|
| 604 | ret = RunCommand('g.mapset',
|
|---|
| 605 | read=True,
|
|---|
| 606 | flags='l',
|
|---|
| 607 | location=location,
|
|---|
| 608 | dbase=dbase)
|
|---|
| 609 |
|
|---|
| 610 | if not ret:
|
|---|
| 611 | return listOfMapsets
|
|---|
| 612 |
|
|---|
| 613 | for line in ret.rstrip().splitlines():
|
|---|
| 614 | listOfMapsets += line.split(' ')
|
|---|
| 615 | else:
|
|---|
| 616 | for mapset in glob.glob(os.path.join(dbase, location, "*")):
|
|---|
| 617 | if os.path.isdir(mapset) and os.path.isfile(
|
|---|
| 618 | os.path.join(dbase, location, mapset, "WIND")):
|
|---|
| 619 | listOfMapsets.append(os.path.basename(mapset))
|
|---|
| 620 |
|
|---|
| 621 | ListSortLower(listOfMapsets)
|
|---|
| 622 | return listOfMapsets
|
|---|
| 623 |
|
|---|
| 624 |
|
|---|
| 625 | def GetColorTables():
|
|---|
| 626 | """Get list of color tables"""
|
|---|
| 627 | ret = RunCommand('r.colors',
|
|---|
| 628 | read=True,
|
|---|
| 629 | flags='l')
|
|---|
| 630 | if not ret:
|
|---|
| 631 | return list()
|
|---|
| 632 |
|
|---|
| 633 | return ret.splitlines()
|
|---|
| 634 |
|
|---|
| 635 |
|
|---|
| 636 | def _getGDALFormats():
|
|---|
| 637 | """Get dictionary of avaialble GDAL drivers"""
|
|---|
| 638 | try:
|
|---|
| 639 | ret = grass.read_command('r.in.gdal',
|
|---|
| 640 | quiet=True,
|
|---|
| 641 | flags='f')
|
|---|
| 642 | except:
|
|---|
| 643 | ret = None
|
|---|
| 644 |
|
|---|
| 645 | return _parseFormats(ret), _parseFormats(ret, writableOnly=True)
|
|---|
| 646 |
|
|---|
| 647 |
|
|---|
| 648 | def _getOGRFormats():
|
|---|
| 649 | """Get dictionary of avaialble OGR drivers"""
|
|---|
| 650 | try:
|
|---|
| 651 | ret = grass.read_command('v.in.ogr',
|
|---|
| 652 | quiet=True,
|
|---|
| 653 | flags='f')
|
|---|
| 654 | except:
|
|---|
| 655 | ret = None
|
|---|
| 656 |
|
|---|
| 657 | return _parseFormats(ret), _parseFormats(ret, writableOnly=True)
|
|---|
| 658 |
|
|---|
| 659 |
|
|---|
| 660 | def _parseFormats(output, writableOnly=False):
|
|---|
| 661 | """Parse r.in.gdal/v.in.ogr -f output"""
|
|---|
| 662 | formats = {'file': list(),
|
|---|
| 663 | 'database': list(),
|
|---|
| 664 | 'protocol': list()
|
|---|
| 665 | }
|
|---|
| 666 |
|
|---|
| 667 | if not output:
|
|---|
| 668 | return formats
|
|---|
| 669 |
|
|---|
| 670 | patt = None
|
|---|
| 671 | if writableOnly:
|
|---|
| 672 | patt = re.compile('\(rw\+?\)$', re.IGNORECASE)
|
|---|
| 673 |
|
|---|
| 674 | for line in output.splitlines():
|
|---|
| 675 | key, name = map(lambda x: x.strip(), line.strip().rsplit(':', -1))
|
|---|
| 676 |
|
|---|
| 677 | if writableOnly and not patt.search(key):
|
|---|
| 678 | continue
|
|---|
| 679 |
|
|---|
| 680 | if name in ('Memory', 'Virtual Raster', 'In Memory Raster'):
|
|---|
| 681 | continue
|
|---|
| 682 | if name in ('PostgreSQL', 'SQLite',
|
|---|
| 683 | 'ODBC', 'ESRI Personal GeoDatabase',
|
|---|
| 684 | 'Rasterlite',
|
|---|
| 685 | 'PostGIS WKT Raster driver',
|
|---|
| 686 | 'PostGIS Raster driver',
|
|---|
| 687 | 'CouchDB',
|
|---|
| 688 | 'MSSQLSpatial',
|
|---|
| 689 | 'FileGDB'):
|
|---|
| 690 | formats['database'].append(name)
|
|---|
| 691 | elif name in ('GeoJSON',
|
|---|
| 692 | 'OGC Web Coverage Service',
|
|---|
| 693 | 'OGC Web Map Service',
|
|---|
| 694 | 'WFS',
|
|---|
| 695 | 'GeoRSS',
|
|---|
| 696 | 'HTTP Fetching Wrapper'):
|
|---|
| 697 | formats['protocol'].append(name)
|
|---|
| 698 | else:
|
|---|
| 699 | formats['file'].append(name)
|
|---|
| 700 |
|
|---|
| 701 | for items in six.itervalues(formats):
|
|---|
| 702 | items.sort()
|
|---|
| 703 |
|
|---|
| 704 | return formats
|
|---|
| 705 |
|
|---|
| 706 | formats = None
|
|---|
| 707 |
|
|---|
| 708 |
|
|---|
| 709 | def GetFormats(writableOnly=False):
|
|---|
| 710 | """Get GDAL/OGR formats"""
|
|---|
| 711 | global formats
|
|---|
| 712 | if not formats:
|
|---|
| 713 | gdalAll, gdalWritable = _getGDALFormats()
|
|---|
| 714 | ogrAll, ogrWritable = _getOGRFormats()
|
|---|
| 715 | formats = {
|
|---|
| 716 | 'all': {
|
|---|
| 717 | 'gdal': gdalAll,
|
|---|
| 718 | 'ogr': ogrAll,
|
|---|
| 719 | },
|
|---|
| 720 | 'writable': {
|
|---|
| 721 | 'gdal': gdalWritable,
|
|---|
| 722 | 'ogr': ogrWritable,
|
|---|
| 723 | },
|
|---|
| 724 | }
|
|---|
| 725 |
|
|---|
| 726 | if writableOnly:
|
|---|
| 727 | return formats['writable']
|
|---|
| 728 |
|
|---|
| 729 | return formats['all']
|
|---|
| 730 |
|
|---|
| 731 |
|
|---|
| 732 | rasterFormatExtension = {
|
|---|
| 733 | 'GeoTIFF': 'tif',
|
|---|
| 734 | 'Erdas Imagine Images (.img)': 'img',
|
|---|
| 735 | 'Ground-based SAR Applications Testbed File Format (.gff)': 'gff',
|
|---|
| 736 | 'Arc/Info Binary Grid': 'adf',
|
|---|
| 737 | 'Portable Network Graphics': 'png',
|
|---|
| 738 | 'JPEG JFIF': 'jpg',
|
|---|
| 739 | 'Japanese DEM (.mem)': 'mem',
|
|---|
| 740 | 'Graphics Interchange Format (.gif)': 'gif',
|
|---|
| 741 | 'X11 PixMap Format': 'xpm',
|
|---|
| 742 | 'MS Windows Device Independent Bitmap': 'bmp',
|
|---|
| 743 | 'SPOT DIMAP': 'dim',
|
|---|
| 744 | 'RadarSat 2 XML Product': 'xml',
|
|---|
| 745 | 'EarthWatch .TIL': 'til',
|
|---|
| 746 | 'ERMapper .ers Labelled': 'ers',
|
|---|
| 747 | 'ERMapper Compressed Wavelets': 'ecw',
|
|---|
| 748 | 'GRIdded Binary (.grb)': 'grb',
|
|---|
| 749 | 'EUMETSAT Archive native (.nat)': 'nat',
|
|---|
| 750 | 'Idrisi Raster A.1': 'rst',
|
|---|
| 751 | 'Golden Software ASCII Grid (.grd)': 'grd',
|
|---|
| 752 | 'Golden Software Binary Grid (.grd)': 'grd',
|
|---|
| 753 | 'Golden Software 7 Binary Grid (.grd)': 'grd',
|
|---|
| 754 | 'R Object Data Store': 'r',
|
|---|
| 755 | 'USGS DOQ (Old Style)': 'doq',
|
|---|
| 756 | 'USGS DOQ (New Style)': 'doq',
|
|---|
| 757 | 'ENVI .hdr Labelled': 'hdr',
|
|---|
| 758 | 'ESRI .hdr Labelled': 'hdr',
|
|---|
| 759 | 'Generic Binary (.hdr Labelled)': 'hdr',
|
|---|
| 760 | 'PCI .aux Labelled': 'aux',
|
|---|
| 761 | 'EOSAT FAST Format': 'fst',
|
|---|
| 762 | 'VTP .bt (Binary Terrain) 1.3 Format': 'bt',
|
|---|
| 763 | 'FARSITE v.4 Landscape File (.lcp)': 'lcp',
|
|---|
| 764 | 'Swedish Grid RIK (.rik)': 'rik',
|
|---|
| 765 | 'USGS Optional ASCII DEM (and CDED)': 'dem',
|
|---|
| 766 | 'Northwood Numeric Grid Format .grd/.tab': '',
|
|---|
| 767 | 'Northwood Classified Grid Format .grc/.tab': '',
|
|---|
| 768 | 'ARC Digitized Raster Graphics': 'arc',
|
|---|
| 769 | 'Magellan topo (.blx)': 'blx',
|
|---|
| 770 | 'SAGA GIS Binary Grid (.sdat)': 'sdat',
|
|---|
| 771 | 'GeoPackage (.gpkg)': 'gpkg'
|
|---|
| 772 | }
|
|---|
| 773 |
|
|---|
| 774 |
|
|---|
| 775 | vectorFormatExtension = {
|
|---|
| 776 | 'ESRI Shapefile': 'shp',
|
|---|
| 777 | 'GeoPackage': 'gpkg',
|
|---|
| 778 | 'UK .NTF': 'ntf',
|
|---|
| 779 | 'SDTS': 'ddf',
|
|---|
| 780 | 'DGN': 'dgn',
|
|---|
| 781 | 'VRT': 'vrt',
|
|---|
| 782 | 'REC': 'rec',
|
|---|
| 783 | 'BNA': 'bna',
|
|---|
| 784 | 'CSV': 'csv',
|
|---|
| 785 | 'GML': 'gml',
|
|---|
| 786 | 'GPX': 'gpx',
|
|---|
| 787 | 'KML': 'kml',
|
|---|
| 788 | 'GMT': 'gmt',
|
|---|
| 789 | 'PGeo': 'mdb',
|
|---|
| 790 | 'XPlane': 'dat',
|
|---|
| 791 | 'AVCBin': 'adf',
|
|---|
| 792 | 'AVCE00': 'e00',
|
|---|
| 793 | 'DXF': 'dxf',
|
|---|
| 794 | 'Geoconcept': 'gxt',
|
|---|
| 795 | 'GeoRSS': 'xml',
|
|---|
| 796 | 'GPSTrackMaker': 'gtm',
|
|---|
| 797 | 'VFK': 'vfk',
|
|---|
| 798 | 'SVG': 'svg'
|
|---|
| 799 | }
|
|---|
| 800 |
|
|---|
| 801 |
|
|---|
| 802 | def GetSettingsPath():
|
|---|
| 803 | """Get full path to the settings directory
|
|---|
| 804 | """
|
|---|
| 805 | try:
|
|---|
| 806 | verFd = open(os.path.join(ETCDIR, "VERSIONNUMBER"))
|
|---|
| 807 | version = int(verFd.readlines()[0].split(' ')[0].split('.')[0])
|
|---|
| 808 | except (IOError, ValueError, TypeError, IndexError) as e:
|
|---|
| 809 | sys.exit(
|
|---|
| 810 | _("ERROR: Unable to determine GRASS version. Details: %s") %
|
|---|
| 811 | e)
|
|---|
| 812 |
|
|---|
| 813 | verFd.close()
|
|---|
| 814 |
|
|---|
| 815 | # keep location of settings files rc and wx in sync with lib/init/grass.py
|
|---|
| 816 | if sys.platform == 'win32':
|
|---|
| 817 | return os.path.join(os.getenv('APPDATA'), 'GRASS%d' % version)
|
|---|
| 818 |
|
|---|
| 819 | return os.path.join(os.getenv('HOME'), '.grass%d' % version)
|
|---|
| 820 |
|
|---|
| 821 |
|
|---|
| 822 | def StoreEnvVariable(key, value=None, envFile=None):
|
|---|
| 823 | """Store environmental variable
|
|---|
| 824 |
|
|---|
| 825 | If value is not given (is None) then environmental variable is
|
|---|
| 826 | unset.
|
|---|
| 827 |
|
|---|
| 828 | :param key: env key
|
|---|
| 829 | :param value: env value
|
|---|
| 830 | :param envFile: path to the environmental file (None for default location)
|
|---|
| 831 | """
|
|---|
| 832 | windows = sys.platform == 'win32'
|
|---|
| 833 | if not envFile:
|
|---|
| 834 | gVersion = grass.version()['version'].split('.', 1)[0]
|
|---|
| 835 | if not windows:
|
|---|
| 836 | envFile = os.path.join(
|
|---|
| 837 | os.getenv('HOME'), '.grass%s' %
|
|---|
| 838 | gVersion, 'bashrc')
|
|---|
| 839 | else:
|
|---|
| 840 | envFile = os.path.join(
|
|---|
| 841 | os.getenv('APPDATA'), 'GRASS%s' %
|
|---|
| 842 | gVersion, 'env.bat')
|
|---|
| 843 |
|
|---|
| 844 | # read env file
|
|---|
| 845 | environ = dict()
|
|---|
| 846 | lineSkipped = list()
|
|---|
| 847 | if os.path.exists(envFile):
|
|---|
| 848 | try:
|
|---|
| 849 | fd = open(envFile)
|
|---|
| 850 | except IOError as e:
|
|---|
| 851 | sys.stderr.write(_("Unable to open file '%s'\n") % envFile)
|
|---|
| 852 | return
|
|---|
| 853 | for line in fd.readlines():
|
|---|
| 854 | line = line.rstrip(os.linesep)
|
|---|
| 855 | try:
|
|---|
| 856 | k, v = map(
|
|---|
| 857 | lambda x: x.strip(), line.split(
|
|---|
| 858 | ' ', 1)[1].split(
|
|---|
| 859 | '=', 1))
|
|---|
| 860 | except Exception as e:
|
|---|
| 861 | sys.stderr.write(_("%s: line skipped - unable to parse '%s'\n"
|
|---|
| 862 | "Reason: %s\n") % (envFile, line, e))
|
|---|
| 863 | lineSkipped.append(line)
|
|---|
| 864 | continue
|
|---|
| 865 | if k in environ:
|
|---|
| 866 | sys.stderr.write(_("Duplicated key: %s\n") % k)
|
|---|
| 867 | environ[k] = v
|
|---|
| 868 |
|
|---|
| 869 | fd.close()
|
|---|
| 870 |
|
|---|
| 871 | # update environmental variables
|
|---|
| 872 | if value is None:
|
|---|
| 873 | if key in environ:
|
|---|
| 874 | del environ[key]
|
|---|
| 875 | else:
|
|---|
| 876 | environ[key] = value
|
|---|
| 877 |
|
|---|
| 878 | # write update env file
|
|---|
| 879 | try:
|
|---|
| 880 | fd = open(envFile, 'w')
|
|---|
| 881 | except IOError as e:
|
|---|
| 882 | sys.stderr.write(_("Unable to create file '%s'\n") % envFile)
|
|---|
| 883 | return
|
|---|
| 884 | if windows:
|
|---|
| 885 | expCmd = 'set'
|
|---|
| 886 | else:
|
|---|
| 887 | expCmd = 'export'
|
|---|
| 888 |
|
|---|
| 889 | for key, value in six.iteritems(environ):
|
|---|
| 890 | fd.write('%s %s=%s\n' % (expCmd, key, value))
|
|---|
| 891 |
|
|---|
| 892 | # write also skipped lines
|
|---|
| 893 | for line in lineSkipped:
|
|---|
| 894 | fd.write(line + os.linesep)
|
|---|
| 895 |
|
|---|
| 896 | fd.close()
|
|---|
| 897 |
|
|---|
| 898 |
|
|---|
| 899 | def SetAddOnPath(addonPath=None, key='PATH'):
|
|---|
| 900 | """Set default AddOn path
|
|---|
| 901 |
|
|---|
| 902 | :param addonPath: path to addons (None for default)
|
|---|
| 903 | :param key: env key - 'PATH' or 'BASE'
|
|---|
| 904 | """
|
|---|
| 905 | gVersion = grass.version()['version'].split('.', 1)[0]
|
|---|
| 906 | # update env file
|
|---|
| 907 | if not addonPath:
|
|---|
| 908 | if sys.platform != 'win32':
|
|---|
| 909 | addonPath = os.path.join(os.path.join(os.getenv('HOME'),
|
|---|
| 910 | '.grass%s' % gVersion,
|
|---|
| 911 | 'addons'))
|
|---|
| 912 | else:
|
|---|
| 913 | addonPath = os.path.join(os.path.join(os.getenv('APPDATA'),
|
|---|
| 914 | 'GRASS%s' % gVersion,
|
|---|
| 915 | 'addons'))
|
|---|
| 916 |
|
|---|
| 917 | StoreEnvVariable(key='GRASS_ADDON_' + key, value=addonPath)
|
|---|
| 918 | os.environ['GRASS_ADDON_' + key] = addonPath
|
|---|
| 919 |
|
|---|
| 920 | # update path
|
|---|
| 921 | if addonPath not in os.environ['PATH']:
|
|---|
| 922 | os.environ['PATH'] = addonPath + os.pathsep + os.environ['PATH']
|
|---|
| 923 |
|
|---|
| 924 |
|
|---|
| 925 | # predefined colors and their names
|
|---|
| 926 | # must be in sync with lib/gis/color_str.c
|
|---|
| 927 | str2rgb = {'aqua': (100, 128, 255),
|
|---|
| 928 | 'black': (0, 0, 0),
|
|---|
| 929 | 'blue': (0, 0, 255),
|
|---|
| 930 | 'brown': (180, 77, 25),
|
|---|
| 931 | 'cyan': (0, 255, 255),
|
|---|
| 932 | 'gray': (128, 128, 128),
|
|---|
| 933 | 'grey': (128, 128, 128),
|
|---|
| 934 | 'green': (0, 255, 0),
|
|---|
| 935 | 'indigo': (0, 128, 255),
|
|---|
| 936 | 'magenta': (255, 0, 255),
|
|---|
| 937 | 'orange': (255, 128, 0),
|
|---|
| 938 | 'red': (255, 0, 0),
|
|---|
| 939 | 'violet': (128, 0, 255),
|
|---|
| 940 | 'purple': (128, 0, 255),
|
|---|
| 941 | 'white': (255, 255, 255),
|
|---|
| 942 | 'yellow': (255, 255, 0)}
|
|---|
| 943 | rgb2str = {}
|
|---|
| 944 | for (s, r) in str2rgb.items():
|
|---|
| 945 | rgb2str[r] = s
|
|---|
| 946 | # ensure that gray value has 'gray' string and not 'grey'
|
|---|
| 947 | rgb2str[str2rgb['gray']] = 'gray'
|
|---|
| 948 | # purple is defined as nickname for violet in lib/gis
|
|---|
| 949 | # (although Wikipedia says that purple is (128, 0, 128))
|
|---|
| 950 | # we will prefer the defined color, not nickname
|
|---|
| 951 | rgb2str[str2rgb['violet']] = 'violet'
|
|---|
| 952 |
|
|---|
| 953 |
|
|---|
| 954 | def color_resolve(color):
|
|---|
| 955 | if len(color) > 0 and color[0] in "0123456789":
|
|---|
| 956 | rgb = tuple(map(int, color.split(':')))
|
|---|
| 957 | label = color
|
|---|
| 958 | else:
|
|---|
| 959 | # Convert color names to RGB
|
|---|
| 960 | try:
|
|---|
| 961 | rgb = str2rgb[color]
|
|---|
| 962 | label = color
|
|---|
| 963 | except KeyError:
|
|---|
| 964 | rgb = (200, 200, 200)
|
|---|
| 965 | label = _('Select Color')
|
|---|
| 966 | return (rgb, label)
|
|---|
| 967 |
|
|---|
| 968 | command2ltype = {'d.rast': 'raster',
|
|---|
| 969 | 'd.rast3d': 'raster_3d',
|
|---|
| 970 | 'd.rgb': 'rgb',
|
|---|
| 971 | 'd.his': 'his',
|
|---|
| 972 | 'd.shade': 'shaded',
|
|---|
| 973 | 'd.legend': 'rastleg',
|
|---|
| 974 | 'd.rast.arrow': 'rastarrow',
|
|---|
| 975 | 'd.rast.num': 'rastnum',
|
|---|
| 976 | 'd.rast.leg': 'maplegend',
|
|---|
| 977 | 'd.vect': 'vector',
|
|---|
| 978 | 'd.vect.thematic': 'thememap',
|
|---|
| 979 | 'd.vect.chart': 'themechart',
|
|---|
| 980 | 'd.grid': 'grid',
|
|---|
| 981 | 'd.geodesic': 'geodesic',
|
|---|
| 982 | 'd.rhumbline': 'rhumb',
|
|---|
| 983 | 'd.labels': 'labels',
|
|---|
| 984 | 'd.barscale': 'barscale',
|
|---|
| 985 | 'd.redraw': 'redraw',
|
|---|
| 986 | 'd.wms': 'wms',
|
|---|
| 987 | 'd.histogram': 'histogram',
|
|---|
| 988 | 'd.colortable': 'colortable',
|
|---|
| 989 | 'd.graph': 'graph',
|
|---|
| 990 | 'd.out.file': 'export',
|
|---|
| 991 | 'd.to.rast': 'torast',
|
|---|
| 992 | 'd.text': 'text',
|
|---|
| 993 | 'd.northarrow': 'northarrow',
|
|---|
| 994 | 'd.polar': 'polar',
|
|---|
| 995 | 'd.legend.vect': 'vectleg'
|
|---|
| 996 | }
|
|---|
| 997 | ltype2command = {}
|
|---|
| 998 | for (cmd, ltype) in command2ltype.items():
|
|---|
| 999 | ltype2command[ltype] = cmd
|
|---|
| 1000 |
|
|---|
| 1001 |
|
|---|
| 1002 | def GetGEventAttribsForHandler(method, event):
|
|---|
| 1003 | """Get attributes from event, which can be used by handler method.
|
|---|
| 1004 |
|
|---|
| 1005 | Be aware of event class attributes.
|
|---|
| 1006 |
|
|---|
| 1007 | :param method: handler method (including self arg)
|
|---|
| 1008 | :param event: event
|
|---|
| 1009 |
|
|---|
| 1010 | :return: (valid kwargs for method,
|
|---|
| 1011 | list of method's args without default value
|
|---|
| 1012 | which were not found among event attributes)
|
|---|
| 1013 | """
|
|---|
| 1014 | args_spec = inspect.getargspec(method)
|
|---|
| 1015 |
|
|---|
| 1016 | args = args_spec[0]
|
|---|
| 1017 |
|
|---|
| 1018 | defaults = []
|
|---|
| 1019 | if args_spec[3]:
|
|---|
| 1020 | defaults = args_spec[3]
|
|---|
| 1021 |
|
|---|
| 1022 | # number of arguments without def value
|
|---|
| 1023 | req_args = len(args) - 1 - len(defaults)
|
|---|
| 1024 |
|
|---|
| 1025 | kwargs = {}
|
|---|
| 1026 | missing_args = []
|
|---|
| 1027 |
|
|---|
| 1028 | for i, a in enumerate(args):
|
|---|
| 1029 | if hasattr(event, a):
|
|---|
| 1030 | kwargs[a] = getattr(event, a)
|
|---|
| 1031 | elif i < req_args:
|
|---|
| 1032 | missing_args.append(a)
|
|---|
| 1033 |
|
|---|
| 1034 | return kwargs, missing_args
|
|---|
| 1035 |
|
|---|
| 1036 |
|
|---|
| 1037 | def PilImageToWxImage(pilImage, copyAlpha=True):
|
|---|
| 1038 | """Convert PIL image to wx.Image
|
|---|
| 1039 |
|
|---|
| 1040 | Based on http://wiki.wxpython.org/WorkingWithImages
|
|---|
| 1041 | """
|
|---|
| 1042 | from gui_core.wrap import EmptyImage
|
|---|
| 1043 | hasAlpha = pilImage.mode[-1] == 'A'
|
|---|
| 1044 | if copyAlpha and hasAlpha: # Make sure there is an alpha layer copy.
|
|---|
| 1045 | wxImage = EmptyImage(*pilImage.size)
|
|---|
| 1046 | pilImageCopyRGBA = pilImage.copy()
|
|---|
| 1047 | pilImageCopyRGB = pilImageCopyRGBA.convert('RGB') # RGBA --> RGB
|
|---|
| 1048 | fn = getattr(
|
|---|
| 1049 | pilImageCopyRGB,
|
|---|
| 1050 | "tobytes",
|
|---|
| 1051 | getattr(
|
|---|
| 1052 | pilImageCopyRGB,
|
|---|
| 1053 | "tostring"))
|
|---|
| 1054 | pilImageRgbData = fn()
|
|---|
| 1055 | wxImage.SetData(pilImageRgbData)
|
|---|
| 1056 | fn = getattr(
|
|---|
| 1057 | pilImageCopyRGBA,
|
|---|
| 1058 | "tobytes",
|
|---|
| 1059 | getattr(
|
|---|
| 1060 | pilImageCopyRGBA,
|
|---|
| 1061 | "tostring"))
|
|---|
| 1062 | # Create layer and insert alpha values.
|
|---|
| 1063 | if wxPythonPhoenix:
|
|---|
| 1064 | wxImage.SetAlpha(fn()[3::4])
|
|---|
| 1065 | else:
|
|---|
| 1066 | wxImage.SetAlphaData(fn()[3::4])
|
|---|
| 1067 |
|
|---|
| 1068 | else: # The resulting image will not have alpha.
|
|---|
| 1069 | wxImage = EmptyImage(*pilImage.size)
|
|---|
| 1070 | pilImageCopy = pilImage.copy()
|
|---|
| 1071 | # Discard any alpha from the PIL image.
|
|---|
| 1072 | pilImageCopyRGB = pilImageCopy.convert('RGB')
|
|---|
| 1073 | fn = getattr(
|
|---|
| 1074 | pilImageCopyRGB,
|
|---|
| 1075 | "tobytes",
|
|---|
| 1076 | getattr(
|
|---|
| 1077 | pilImageCopyRGB,
|
|---|
| 1078 | "tostring"))
|
|---|
| 1079 | pilImageRgbData = fn()
|
|---|
| 1080 | wxImage.SetData(pilImageRgbData)
|
|---|
| 1081 |
|
|---|
| 1082 | return wxImage
|
|---|
| 1083 |
|
|---|
| 1084 |
|
|---|
| 1085 | def autoCropImageFromFile(filename):
|
|---|
| 1086 | """Loads image from file and crops it automatically.
|
|---|
| 1087 |
|
|---|
| 1088 | If PIL is not installed, it does not crop it.
|
|---|
| 1089 |
|
|---|
| 1090 | :param filename: path to file
|
|---|
| 1091 | :return: wx.Image instance
|
|---|
| 1092 | """
|
|---|
| 1093 | try:
|
|---|
| 1094 | from PIL import Image
|
|---|
| 1095 | pilImage = Image.open(filename)
|
|---|
| 1096 | imageBox = pilImage.getbbox()
|
|---|
| 1097 | cropped = pilImage.crop(imageBox)
|
|---|
| 1098 | return PilImageToWxImage(cropped, copyAlpha=True)
|
|---|
| 1099 | except ImportError:
|
|---|
| 1100 | import wx
|
|---|
| 1101 | return wx.Image(filename)
|
|---|
| 1102 |
|
|---|
| 1103 |
|
|---|
| 1104 | def isInRegion(regionA, regionB):
|
|---|
| 1105 | """Tests if 'regionA' is inside of 'regionB'.
|
|---|
| 1106 |
|
|---|
| 1107 | For example, region A is a display region and region B is some reference
|
|---|
| 1108 | region, e.g., a computational region.
|
|---|
| 1109 |
|
|---|
| 1110 | >>> displayRegion = {'n': 223900, 's': 217190, 'w': 630780, 'e': 640690}
|
|---|
| 1111 | >>> compRegion = {'n': 228500, 's': 215000, 'w': 630000, 'e': 645000}
|
|---|
| 1112 | >>> isInRegion(displayRegion, compRegion)
|
|---|
| 1113 | True
|
|---|
| 1114 | >>> displayRegion = {'n':226020, 's': 212610, 'w': 626510, 'e': 646330}
|
|---|
| 1115 | >>> isInRegion(displayRegion, compRegion)
|
|---|
| 1116 | False
|
|---|
| 1117 |
|
|---|
| 1118 | :param regionA: input region A as dictionary
|
|---|
| 1119 | :param regionB: input region B as dictionary
|
|---|
| 1120 |
|
|---|
| 1121 | :return: True if region A is inside of region B
|
|---|
| 1122 | :return: False othewise
|
|---|
| 1123 | """
|
|---|
| 1124 | if regionA['s'] >= regionB['s'] and \
|
|---|
| 1125 | regionA['n'] <= regionB['n'] and \
|
|---|
| 1126 | regionA['w'] >= regionB['w'] and \
|
|---|
| 1127 | regionA['e'] <= regionB['e']:
|
|---|
| 1128 | return True
|
|---|
| 1129 |
|
|---|
| 1130 | return False
|
|---|
| 1131 |
|
|---|
| 1132 |
|
|---|
| 1133 | def do_doctest_gettext_workaround():
|
|---|
| 1134 | """Setups environment for doing a doctest with gettext usage.
|
|---|
| 1135 |
|
|---|
| 1136 | When using gettext with dynamically defined underscore function
|
|---|
| 1137 | (`_("For translation")`), doctest does not work properly. One option is to
|
|---|
| 1138 | use `import as` instead of dynamically defined underscore function but this
|
|---|
| 1139 | would require change all modules which are used by tested module. This
|
|---|
| 1140 | should be considered for the future. The second option is to define dummy
|
|---|
| 1141 | underscore function and one other function which creates the right
|
|---|
| 1142 | environment to satisfy all. This is done by this function.
|
|---|
| 1143 | """
|
|---|
| 1144 | def new_displayhook(string):
|
|---|
| 1145 | """A replacement for default `sys.displayhook`"""
|
|---|
| 1146 | if string is not None:
|
|---|
| 1147 | sys.stdout.write("%r\n" % (string,))
|
|---|
| 1148 |
|
|---|
| 1149 | def new_translator(string):
|
|---|
| 1150 | """A fake gettext underscore function."""
|
|---|
| 1151 | return string
|
|---|
| 1152 |
|
|---|
| 1153 | sys.displayhook = new_displayhook
|
|---|
| 1154 |
|
|---|
| 1155 | import __builtin__
|
|---|
| 1156 | __builtin__._ = new_translator
|
|---|
| 1157 |
|
|---|
| 1158 |
|
|---|
| 1159 | def doc_test():
|
|---|
| 1160 | """Tests the module using doctest
|
|---|
| 1161 |
|
|---|
| 1162 | :return: a number of failed tests
|
|---|
| 1163 | """
|
|---|
| 1164 | import doctest
|
|---|
| 1165 | do_doctest_gettext_workaround()
|
|---|
| 1166 | return doctest.testmod().failed
|
|---|
| 1167 |
|
|---|
| 1168 |
|
|---|
| 1169 | def registerPid(pid):
|
|---|
| 1170 | """Register process id as GUI_PID GRASS variable
|
|---|
| 1171 |
|
|---|
| 1172 | :param: pid process id
|
|---|
| 1173 | """
|
|---|
| 1174 | env = grass.gisenv()
|
|---|
| 1175 | guiPid = []
|
|---|
| 1176 | if 'GUI_PID' in env:
|
|---|
| 1177 | guiPid = env['GUI_PID'].split(',')
|
|---|
| 1178 | guiPid.append(str(pid))
|
|---|
| 1179 | grass.run_command('g.gisenv', set='GUI_PID={0}'.format(','.join(guiPid)))
|
|---|
| 1180 |
|
|---|
| 1181 |
|
|---|
| 1182 | def unregisterPid(pid):
|
|---|
| 1183 | """Unregister process id from GUI_PID GRASS variable
|
|---|
| 1184 |
|
|---|
| 1185 | :param: pid process id
|
|---|
| 1186 | """
|
|---|
| 1187 | env = grass.gisenv()
|
|---|
| 1188 | if 'GUI_PID' not in env:
|
|---|
| 1189 | return
|
|---|
| 1190 |
|
|---|
| 1191 | guiPid = env['GUI_PID'].split(',')
|
|---|
| 1192 | pid = str(os.getpid())
|
|---|
| 1193 | if pid in guiPid:
|
|---|
| 1194 | guiPid.remove(pid)
|
|---|
| 1195 | grass.run_command(
|
|---|
| 1196 | 'g.gisenv',
|
|---|
| 1197 | set='GUI_PID={0}'.format(
|
|---|
| 1198 | ','.join(guiPid)))
|
|---|
| 1199 |
|
|---|
| 1200 | if __name__ == '__main__':
|
|---|
| 1201 | sys.exit(doc_test())
|
|---|