Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/plot/gmtpy.py: 77%

1646 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2024-02-05 09:37 +0000

1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5''' 

6A Python interface to GMT. 

7''' 

8 

9# This file has been part of GmtPy (http://emolch.github.io/gmtpy/) 

10# See there for copying and licensing information. 

11 

12import subprocess 

13try: 

14 from StringIO import StringIO as BytesIO 

15except ImportError: 

16 from io import BytesIO 

17import re 

18import os 

19import sys 

20import shutil 

21from os.path import join as pjoin 

22import tempfile 

23import random 

24import logging 

25import math 

26import numpy as num 

27import copy 

28from select import select 

29try: 

30 from scipy.io import netcdf_file 

31except ImportError: 

32 from scipy.io.netcdf import netcdf_file 

33 

34from pyrocko import ExternalProgramMissing 

35from . import AutoScaler 

36 

37 

38find_bb = re.compile(br'%%BoundingBox:((\s+[-0-9]+){4})') 

39find_hiresbb = re.compile(br'%%HiResBoundingBox:((\s+[-0-9.]+){4})') 

40 

41 

42encoding_gmt_to_python = { 

43 'isolatin1+': 'iso-8859-1', 

44 'standard+': 'ascii', 

45 'isolatin1': 'iso-8859-1', 

46 'standard': 'ascii'} 

47 

48for i in range(1, 11): 

49 encoding_gmt_to_python['iso-8859-%i' % i] = 'iso-8859-%i' % i 

50 

51 

52def have_gmt(): 

53 try: 

54 get_gmt_installation('newest') 

55 return True 

56 

57 except GMTInstallationProblem: 

58 return False 

59 

60 

61def check_have_gmt(): 

62 if not have_gmt(): 

63 raise ExternalProgramMissing('GMT is not installed or cannot be found') 

64 

65 

66def have_pixmaptools(): 

67 for prog in [['pdftocairo'], ['convert'], ['gs', '-h']]: 

68 try: 

69 p = subprocess.Popen( 

70 prog, 

71 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 

72 

73 (stdout, stderr) = p.communicate() 

74 

75 except OSError: 

76 return False 

77 

78 return True 

79 

80 

81class GmtPyError(Exception): 

82 pass 

83 

84 

85class GMTError(GmtPyError): 

86 pass 

87 

88 

89class GMTInstallationProblem(GmtPyError): 

90 pass 

91 

92 

93def convert_graph(in_filename, out_filename, resolution=75., oversample=2., 

94 width=None, height=None, size=None): 

95 

96 _, tmp_filename_base = tempfile.mkstemp() 

97 

98 try: 

99 if out_filename.endswith('.svg'): 

100 fmt_arg = '-svg' 

101 tmp_filename = tmp_filename_base 

102 oversample = 1.0 

103 else: 

104 fmt_arg = '-png' 

105 tmp_filename = tmp_filename_base + '-1.png' 

106 

107 if size is not None: 

108 scale_args = ['-scale-to', '%i' % int(round(size*oversample))] 

109 elif width is not None: 

110 scale_args = ['-scale-to-x', '%i' % int(round(width*oversample))] 

111 elif height is not None: 

112 scale_args = ['-scale-to-y', '%i' % int(round(height*oversample))] 

113 else: 

114 scale_args = ['-r', '%i' % int(round(resolution * oversample))] 

115 

116 try: 

117 subprocess.check_call( 

118 ['pdftocairo'] + scale_args + 

119 [fmt_arg, in_filename, tmp_filename_base]) 

120 except OSError as e: 

121 raise GmtPyError( 

122 'Cannot start `pdftocairo`, is it installed? (%s)' % str(e)) 

123 

124 if oversample > 1.: 

125 try: 

126 subprocess.check_call([ 

127 'convert', 

128 tmp_filename, 

129 '-resize', '%i%%' % int(round(100.0/oversample)), 

130 out_filename]) 

131 except OSError as e: 

132 raise GmtPyError( 

133 'Cannot start `convert`, is it installed? (%s)' % str(e)) 

134 

135 else: 

136 if out_filename.endswith('.png') or out_filename.endswith('.svg'): 

137 shutil.move(tmp_filename, out_filename) 

138 else: 

139 try: 

140 subprocess.check_call( 

141 ['convert', tmp_filename, out_filename]) 

142 except Exception as e: 

143 raise GmtPyError( 

144 'Cannot start `convert`, is it installed? (%s)' 

145 % str(e)) 

146 

147 except Exception: 

148 raise 

149 

150 finally: 

151 if os.path.exists(tmp_filename_base): 

152 os.remove(tmp_filename_base) 

153 

154 if os.path.exists(tmp_filename): 

155 os.remove(tmp_filename) 

156 

157 

158def get_bbox(s): 

159 for pat in [find_hiresbb, find_bb]: 

160 m = pat.search(s) 

161 if m: 

162 bb = [float(x) for x in m.group(1).split()] 

163 return bb 

164 

165 raise GmtPyError('Cannot find bbox') 

166 

167 

168def replace_bbox(bbox, *args): 

169 

170 def repl(m): 

171 if m.group(1): 

172 return ('%%HiResBoundingBox: ' + ' '.join( 

173 '%.3f' % float(x) for x in bbox)).encode('ascii') 

174 else: 

175 return ('%%%%BoundingBox: %i %i %i %i' % ( 

176 int(math.floor(bbox[0])), 

177 int(math.floor(bbox[1])), 

178 int(math.ceil(bbox[2])), 

179 int(math.ceil(bbox[3])))).encode('ascii') 

180 

181 pat = re.compile(br'%%(HiRes)?BoundingBox:((\s+[0-9.]+){4})') 

182 if len(args) == 1: 

183 s = args[0] 

184 return pat.sub(repl, s) 

185 

186 else: 

187 fin, fout = args 

188 nn = 0 

189 for line in fin: 

190 line, n = pat.subn(repl, line) 

191 nn += n 

192 fout.write(line) 

193 if nn == 2: 

194 break 

195 

196 if nn == 2: 

197 for line in fin: 

198 fout.write(line) 

199 

200 

201def escape_shell_arg(s): 

202 ''' 

203 This function should be used for debugging output only - it could be 

204 insecure. 

205 ''' 

206 

207 if re.search(r'[^a-zA-Z0-9._/=-]', s): 

208 return "'" + s.replace("'", "'\\''") + "'" 

209 else: 

210 return s 

211 

212 

213def escape_shell_args(args): 

214 ''' 

215 This function should be used for debugging output only - it could be 

216 insecure. 

217 ''' 

218 

219 return ' '.join([escape_shell_arg(x) for x in args]) 

220 

221 

222golden_ratio = 1.61803 

223 

224# units in points 

225_units = { 

226 'i': 72., 

227 'c': 72./2.54, 

228 'm': 72.*100./2.54, 

229 'p': 1.} 

230 

231inch = _units['i'] 

232''' 

233Unit cm in points. 

234''' 

235 

236 

237cm = _units['c'] 

238''' 

239Unit inch in points. 

240''' 

241 

242# some awsome colors 

243tango_colors = { 

244 'butter1': (252, 233, 79), 

245 'butter2': (237, 212, 0), 

246 'butter3': (196, 160, 0), 

247 'chameleon1': (138, 226, 52), 

248 'chameleon2': (115, 210, 22), 

249 'chameleon3': (78, 154, 6), 

250 'orange1': (252, 175, 62), 

251 'orange2': (245, 121, 0), 

252 'orange3': (206, 92, 0), 

253 'skyblue1': (114, 159, 207), 

254 'skyblue2': (52, 101, 164), 

255 'skyblue3': (32, 74, 135), 

256 'plum1': (173, 127, 168), 

257 'plum2': (117, 80, 123), 

258 'plum3': (92, 53, 102), 

259 'chocolate1': (233, 185, 110), 

260 'chocolate2': (193, 125, 17), 

261 'chocolate3': (143, 89, 2), 

262 'scarletred1': (239, 41, 41), 

263 'scarletred2': (204, 0, 0), 

264 'scarletred3': (164, 0, 0), 

265 'aluminium1': (238, 238, 236), 

266 'aluminium2': (211, 215, 207), 

267 'aluminium3': (186, 189, 182), 

268 'aluminium4': (136, 138, 133), 

269 'aluminium5': (85, 87, 83), 

270 'aluminium6': (46, 52, 54) 

271} 

272 

273graph_colors = [tango_colors[_x] for _x in ( 

274 'scarletred2', 'skyblue3', 'chameleon3', 'orange2', 'plum2', 'chocolate2', 

275 'butter2')] 

276 

277 

278def color(x=None): 

279 ''' 

280 Generate a string for GMT option arguments expecting a color. 

281 

282 If ``x`` is None, a random color is returned. If it is an integer, the 

283 corresponding ``gmtpy.graph_colors[x]`` or black returned. If it is a 

284 string and the corresponding ``gmtpy.tango_colors[x]`` exists, this is 

285 returned, or the string is passed through. If ``x`` is a tuple, it is 

286 transformed into the string form which GMT expects. 

287 ''' 

288 

289 if x is None: 

290 return '%i/%i/%i' % tuple(random.randint(0, 255) for _ in 'rgb') 

291 

292 if isinstance(x, int): 

293 if 0 <= x < len(graph_colors): 

294 return '%i/%i/%i' % graph_colors[x] 

295 else: 

296 return '0/0/0' 

297 

298 elif isinstance(x, str): 

299 if x in tango_colors: 

300 return '%i/%i/%i' % tango_colors[x] 

301 else: 

302 return x 

303 

304 return '%i/%i/%i' % x 

305 

306 

307def color_tup(x=None): 

308 if x is None: 

309 return tuple([random.randint(0, 255) for _x in 'rgb']) 

310 

311 if isinstance(x, int): 

312 if 0 <= x < len(graph_colors): 

313 return graph_colors[x] 

314 else: 

315 return (0, 0, 0) 

316 

317 elif isinstance(x, str): 

318 if x in tango_colors: 

319 return tango_colors[x] 

320 

321 return x 

322 

323 

324_gmt_installations = {} 

325 

326# Set fixed installation(s) to use... 

327# (use this, if you want to use different GMT versions simultaneously.) 

328 

329# _gmt_installations['4.2.1'] = {'home': '/sw/etch-ia32/gmt-4.2.1', 

330# 'bin': '/sw/etch-ia32/gmt-4.2.1/bin'} 

331# _gmt_installations['4.3.0'] = {'home': '/sw/etch-ia32/gmt-4.3.0', 

332# 'bin': '/sw/etch-ia32/gmt-4.3.0/bin'} 

333# _gmt_installations['6.0.0'] = {'home': '/usr/share/gmt', 

334# 'bin': '/usr/bin' } 

335 

336# ... or let GmtPy autodetect GMT via $PATH and $GMTHOME 

337 

338 

339def key_version(a): 

340 a = a.split('_')[0] # get rid of revision id 

341 return [int(x) for x in a.split('.')] 

342 

343 

344def newest_installed_gmt_version(): 

345 return sorted(_gmt_installations.keys(), key=key_version)[-1] 

346 

347 

348def all_installed_gmt_versions(): 

349 return sorted(_gmt_installations.keys(), key=key_version) 

350 

351 

352# To have consistent defaults, they are hardcoded here and should not be 

353# changed. 

354 

355_gmt_defaults_by_version = {} 

356_gmt_defaults_by_version['4.2.1'] = r''' 

357# 

358# GMT-SYSTEM 4.2.1 Defaults file 

359# 

360#-------- Plot Media Parameters ------------- 

361PAGE_COLOR = 255/255/255 

362PAGE_ORIENTATION = portrait 

363PAPER_MEDIA = a4+ 

364#-------- Basemap Annotation Parameters ------ 

365ANNOT_MIN_ANGLE = 20 

366ANNOT_MIN_SPACING = 0 

367ANNOT_FONT_PRIMARY = Helvetica 

368ANNOT_FONT_SIZE = 12p 

369ANNOT_OFFSET_PRIMARY = 0.075i 

370ANNOT_FONT_SECONDARY = Helvetica 

371ANNOT_FONT_SIZE_SECONDARY = 16p 

372ANNOT_OFFSET_SECONDARY = 0.075i 

373DEGREE_SYMBOL = ring 

374HEADER_FONT = Helvetica 

375HEADER_FONT_SIZE = 36p 

376HEADER_OFFSET = 0.1875i 

377LABEL_FONT = Helvetica 

378LABEL_FONT_SIZE = 14p 

379LABEL_OFFSET = 0.1125i 

380OBLIQUE_ANNOTATION = 1 

381PLOT_CLOCK_FORMAT = hh:mm:ss 

382PLOT_DATE_FORMAT = yyyy-mm-dd 

383PLOT_DEGREE_FORMAT = +ddd:mm:ss 

384Y_AXIS_TYPE = hor_text 

385#-------- Basemap Layout Parameters --------- 

386BASEMAP_AXES = WESN 

387BASEMAP_FRAME_RGB = 0/0/0 

388BASEMAP_TYPE = plain 

389FRAME_PEN = 1.25p 

390FRAME_WIDTH = 0.075i 

391GRID_CROSS_SIZE_PRIMARY = 0i 

392GRID_CROSS_SIZE_SECONDARY = 0i 

393GRID_PEN_PRIMARY = 0.25p 

394GRID_PEN_SECONDARY = 0.5p 

395MAP_SCALE_HEIGHT = 0.075i 

396TICK_LENGTH = 0.075i 

397POLAR_CAP = 85/90 

398TICK_PEN = 0.5p 

399X_AXIS_LENGTH = 9i 

400Y_AXIS_LENGTH = 6i 

401X_ORIGIN = 1i 

402Y_ORIGIN = 1i 

403UNIX_TIME = FALSE 

404UNIX_TIME_POS = -0.75i/-0.75i 

405#-------- Color System Parameters ----------- 

406COLOR_BACKGROUND = 0/0/0 

407COLOR_FOREGROUND = 255/255/255 

408COLOR_NAN = 128/128/128 

409COLOR_IMAGE = adobe 

410COLOR_MODEL = rgb 

411HSV_MIN_SATURATION = 1 

412HSV_MAX_SATURATION = 0.1 

413HSV_MIN_VALUE = 0.3 

414HSV_MAX_VALUE = 1 

415#-------- PostScript Parameters ------------- 

416CHAR_ENCODING = ISOLatin1+ 

417DOTS_PR_INCH = 300 

418N_COPIES = 1 

419PS_COLOR = rgb 

420PS_IMAGE_COMPRESS = none 

421PS_IMAGE_FORMAT = ascii 

422PS_LINE_CAP = round 

423PS_LINE_JOIN = miter 

424PS_MITER_LIMIT = 35 

425PS_VERBOSE = FALSE 

426GLOBAL_X_SCALE = 1 

427GLOBAL_Y_SCALE = 1 

428#-------- I/O Format Parameters ------------- 

429D_FORMAT = %lg 

430FIELD_DELIMITER = tab 

431GRIDFILE_SHORTHAND = FALSE 

432GRID_FORMAT = nf 

433INPUT_CLOCK_FORMAT = hh:mm:ss 

434INPUT_DATE_FORMAT = yyyy-mm-dd 

435IO_HEADER = FALSE 

436N_HEADER_RECS = 1 

437OUTPUT_CLOCK_FORMAT = hh:mm:ss 

438OUTPUT_DATE_FORMAT = yyyy-mm-dd 

439OUTPUT_DEGREE_FORMAT = +D 

440XY_TOGGLE = FALSE 

441#-------- Projection Parameters ------------- 

442ELLIPSOID = WGS-84 

443MAP_SCALE_FACTOR = default 

444MEASURE_UNIT = inch 

445#-------- Calendar/Time Parameters ---------- 

446TIME_FORMAT_PRIMARY = full 

447TIME_FORMAT_SECONDARY = full 

448TIME_EPOCH = 2000-01-01T00:00:00 

449TIME_IS_INTERVAL = OFF 

450TIME_INTERVAL_FRACTION = 0.5 

451TIME_LANGUAGE = us 

452TIME_SYSTEM = other 

453TIME_UNIT = d 

454TIME_WEEK_START = Sunday 

455Y2K_OFFSET_YEAR = 1950 

456#-------- Miscellaneous Parameters ---------- 

457HISTORY = TRUE 

458INTERPOLANT = akima 

459LINE_STEP = 0.01i 

460VECTOR_SHAPE = 0 

461VERBOSE = FALSE''' 

462 

463_gmt_defaults_by_version['4.3.0'] = r''' 

464# 

465# GMT-SYSTEM 4.3.0 Defaults file 

466# 

467#-------- Plot Media Parameters ------------- 

468PAGE_COLOR = 255/255/255 

469PAGE_ORIENTATION = portrait 

470PAPER_MEDIA = a4+ 

471#-------- Basemap Annotation Parameters ------ 

472ANNOT_MIN_ANGLE = 20 

473ANNOT_MIN_SPACING = 0 

474ANNOT_FONT_PRIMARY = Helvetica 

475ANNOT_FONT_SIZE_PRIMARY = 12p 

476ANNOT_OFFSET_PRIMARY = 0.075i 

477ANNOT_FONT_SECONDARY = Helvetica 

478ANNOT_FONT_SIZE_SECONDARY = 16p 

479ANNOT_OFFSET_SECONDARY = 0.075i 

480DEGREE_SYMBOL = ring 

481HEADER_FONT = Helvetica 

482HEADER_FONT_SIZE = 36p 

483HEADER_OFFSET = 0.1875i 

484LABEL_FONT = Helvetica 

485LABEL_FONT_SIZE = 14p 

486LABEL_OFFSET = 0.1125i 

487OBLIQUE_ANNOTATION = 1 

488PLOT_CLOCK_FORMAT = hh:mm:ss 

489PLOT_DATE_FORMAT = yyyy-mm-dd 

490PLOT_DEGREE_FORMAT = +ddd:mm:ss 

491Y_AXIS_TYPE = hor_text 

492#-------- Basemap Layout Parameters --------- 

493BASEMAP_AXES = WESN 

494BASEMAP_FRAME_RGB = 0/0/0 

495BASEMAP_TYPE = plain 

496FRAME_PEN = 1.25p 

497FRAME_WIDTH = 0.075i 

498GRID_CROSS_SIZE_PRIMARY = 0i 

499GRID_PEN_PRIMARY = 0.25p 

500GRID_CROSS_SIZE_SECONDARY = 0i 

501GRID_PEN_SECONDARY = 0.5p 

502MAP_SCALE_HEIGHT = 0.075i 

503POLAR_CAP = 85/90 

504TICK_LENGTH = 0.075i 

505TICK_PEN = 0.5p 

506X_AXIS_LENGTH = 9i 

507Y_AXIS_LENGTH = 6i 

508X_ORIGIN = 1i 

509Y_ORIGIN = 1i 

510UNIX_TIME = FALSE 

511UNIX_TIME_POS = BL/-0.75i/-0.75i 

512UNIX_TIME_FORMAT = %Y %b %d %H:%M:%S 

513#-------- Color System Parameters ----------- 

514COLOR_BACKGROUND = 0/0/0 

515COLOR_FOREGROUND = 255/255/255 

516COLOR_NAN = 128/128/128 

517COLOR_IMAGE = adobe 

518COLOR_MODEL = rgb 

519HSV_MIN_SATURATION = 1 

520HSV_MAX_SATURATION = 0.1 

521HSV_MIN_VALUE = 0.3 

522HSV_MAX_VALUE = 1 

523#-------- PostScript Parameters ------------- 

524CHAR_ENCODING = ISOLatin1+ 

525DOTS_PR_INCH = 300 

526N_COPIES = 1 

527PS_COLOR = rgb 

528PS_IMAGE_COMPRESS = none 

529PS_IMAGE_FORMAT = ascii 

530PS_LINE_CAP = round 

531PS_LINE_JOIN = miter 

532PS_MITER_LIMIT = 35 

533PS_VERBOSE = FALSE 

534GLOBAL_X_SCALE = 1 

535GLOBAL_Y_SCALE = 1 

536#-------- I/O Format Parameters ------------- 

537D_FORMAT = %lg 

538FIELD_DELIMITER = tab 

539GRIDFILE_SHORTHAND = FALSE 

540GRID_FORMAT = nf 

541INPUT_CLOCK_FORMAT = hh:mm:ss 

542INPUT_DATE_FORMAT = yyyy-mm-dd 

543IO_HEADER = FALSE 

544N_HEADER_RECS = 1 

545OUTPUT_CLOCK_FORMAT = hh:mm:ss 

546OUTPUT_DATE_FORMAT = yyyy-mm-dd 

547OUTPUT_DEGREE_FORMAT = +D 

548XY_TOGGLE = FALSE 

549#-------- Projection Parameters ------------- 

550ELLIPSOID = WGS-84 

551MAP_SCALE_FACTOR = default 

552MEASURE_UNIT = inch 

553#-------- Calendar/Time Parameters ---------- 

554TIME_FORMAT_PRIMARY = full 

555TIME_FORMAT_SECONDARY = full 

556TIME_EPOCH = 2000-01-01T00:00:00 

557TIME_IS_INTERVAL = OFF 

558TIME_INTERVAL_FRACTION = 0.5 

559TIME_LANGUAGE = us 

560TIME_UNIT = d 

561TIME_WEEK_START = Sunday 

562Y2K_OFFSET_YEAR = 1950 

563#-------- Miscellaneous Parameters ---------- 

564HISTORY = TRUE 

565INTERPOLANT = akima 

566LINE_STEP = 0.01i 

567VECTOR_SHAPE = 0 

568VERBOSE = FALSE''' 

569 

570 

571_gmt_defaults_by_version['4.3.1'] = r''' 

572# 

573# GMT-SYSTEM 4.3.1 Defaults file 

574# 

575#-------- Plot Media Parameters ------------- 

576PAGE_COLOR = 255/255/255 

577PAGE_ORIENTATION = portrait 

578PAPER_MEDIA = a4+ 

579#-------- Basemap Annotation Parameters ------ 

580ANNOT_MIN_ANGLE = 20 

581ANNOT_MIN_SPACING = 0 

582ANNOT_FONT_PRIMARY = Helvetica 

583ANNOT_FONT_SIZE_PRIMARY = 12p 

584ANNOT_OFFSET_PRIMARY = 0.075i 

585ANNOT_FONT_SECONDARY = Helvetica 

586ANNOT_FONT_SIZE_SECONDARY = 16p 

587ANNOT_OFFSET_SECONDARY = 0.075i 

588DEGREE_SYMBOL = ring 

589HEADER_FONT = Helvetica 

590HEADER_FONT_SIZE = 36p 

591HEADER_OFFSET = 0.1875i 

592LABEL_FONT = Helvetica 

593LABEL_FONT_SIZE = 14p 

594LABEL_OFFSET = 0.1125i 

595OBLIQUE_ANNOTATION = 1 

596PLOT_CLOCK_FORMAT = hh:mm:ss 

597PLOT_DATE_FORMAT = yyyy-mm-dd 

598PLOT_DEGREE_FORMAT = +ddd:mm:ss 

599Y_AXIS_TYPE = hor_text 

600#-------- Basemap Layout Parameters --------- 

601BASEMAP_AXES = WESN 

602BASEMAP_FRAME_RGB = 0/0/0 

603BASEMAP_TYPE = plain 

604FRAME_PEN = 1.25p 

605FRAME_WIDTH = 0.075i 

606GRID_CROSS_SIZE_PRIMARY = 0i 

607GRID_PEN_PRIMARY = 0.25p 

608GRID_CROSS_SIZE_SECONDARY = 0i 

609GRID_PEN_SECONDARY = 0.5p 

610MAP_SCALE_HEIGHT = 0.075i 

611POLAR_CAP = 85/90 

612TICK_LENGTH = 0.075i 

613TICK_PEN = 0.5p 

614X_AXIS_LENGTH = 9i 

615Y_AXIS_LENGTH = 6i 

616X_ORIGIN = 1i 

617Y_ORIGIN = 1i 

618UNIX_TIME = FALSE 

619UNIX_TIME_POS = BL/-0.75i/-0.75i 

620UNIX_TIME_FORMAT = %Y %b %d %H:%M:%S 

621#-------- Color System Parameters ----------- 

622COLOR_BACKGROUND = 0/0/0 

623COLOR_FOREGROUND = 255/255/255 

624COLOR_NAN = 128/128/128 

625COLOR_IMAGE = adobe 

626COLOR_MODEL = rgb 

627HSV_MIN_SATURATION = 1 

628HSV_MAX_SATURATION = 0.1 

629HSV_MIN_VALUE = 0.3 

630HSV_MAX_VALUE = 1 

631#-------- PostScript Parameters ------------- 

632CHAR_ENCODING = ISOLatin1+ 

633DOTS_PR_INCH = 300 

634N_COPIES = 1 

635PS_COLOR = rgb 

636PS_IMAGE_COMPRESS = none 

637PS_IMAGE_FORMAT = ascii 

638PS_LINE_CAP = round 

639PS_LINE_JOIN = miter 

640PS_MITER_LIMIT = 35 

641PS_VERBOSE = FALSE 

642GLOBAL_X_SCALE = 1 

643GLOBAL_Y_SCALE = 1 

644#-------- I/O Format Parameters ------------- 

645D_FORMAT = %lg 

646FIELD_DELIMITER = tab 

647GRIDFILE_SHORTHAND = FALSE 

648GRID_FORMAT = nf 

649INPUT_CLOCK_FORMAT = hh:mm:ss 

650INPUT_DATE_FORMAT = yyyy-mm-dd 

651IO_HEADER = FALSE 

652N_HEADER_RECS = 1 

653OUTPUT_CLOCK_FORMAT = hh:mm:ss 

654OUTPUT_DATE_FORMAT = yyyy-mm-dd 

655OUTPUT_DEGREE_FORMAT = +D 

656XY_TOGGLE = FALSE 

657#-------- Projection Parameters ------------- 

658ELLIPSOID = WGS-84 

659MAP_SCALE_FACTOR = default 

660MEASURE_UNIT = inch 

661#-------- Calendar/Time Parameters ---------- 

662TIME_FORMAT_PRIMARY = full 

663TIME_FORMAT_SECONDARY = full 

664TIME_EPOCH = 2000-01-01T00:00:00 

665TIME_IS_INTERVAL = OFF 

666TIME_INTERVAL_FRACTION = 0.5 

667TIME_LANGUAGE = us 

668TIME_UNIT = d 

669TIME_WEEK_START = Sunday 

670Y2K_OFFSET_YEAR = 1950 

671#-------- Miscellaneous Parameters ---------- 

672HISTORY = TRUE 

673INTERPOLANT = akima 

674LINE_STEP = 0.01i 

675VECTOR_SHAPE = 0 

676VERBOSE = FALSE''' 

677 

678 

679_gmt_defaults_by_version['4.4.0'] = r''' 

680# 

681# GMT-SYSTEM 4.4.0 [64-bit] Defaults file 

682# 

683#-------- Plot Media Parameters ------------- 

684PAGE_COLOR = 255/255/255 

685PAGE_ORIENTATION = portrait 

686PAPER_MEDIA = a4+ 

687#-------- Basemap Annotation Parameters ------ 

688ANNOT_MIN_ANGLE = 20 

689ANNOT_MIN_SPACING = 0 

690ANNOT_FONT_PRIMARY = Helvetica 

691ANNOT_FONT_SIZE_PRIMARY = 14p 

692ANNOT_OFFSET_PRIMARY = 0.075i 

693ANNOT_FONT_SECONDARY = Helvetica 

694ANNOT_FONT_SIZE_SECONDARY = 16p 

695ANNOT_OFFSET_SECONDARY = 0.075i 

696DEGREE_SYMBOL = ring 

697HEADER_FONT = Helvetica 

698HEADER_FONT_SIZE = 36p 

699HEADER_OFFSET = 0.1875i 

700LABEL_FONT = Helvetica 

701LABEL_FONT_SIZE = 14p 

702LABEL_OFFSET = 0.1125i 

703OBLIQUE_ANNOTATION = 1 

704PLOT_CLOCK_FORMAT = hh:mm:ss 

705PLOT_DATE_FORMAT = yyyy-mm-dd 

706PLOT_DEGREE_FORMAT = +ddd:mm:ss 

707Y_AXIS_TYPE = hor_text 

708#-------- Basemap Layout Parameters --------- 

709BASEMAP_AXES = WESN 

710BASEMAP_FRAME_RGB = 0/0/0 

711BASEMAP_TYPE = plain 

712FRAME_PEN = 1.25p 

713FRAME_WIDTH = 0.075i 

714GRID_CROSS_SIZE_PRIMARY = 0i 

715GRID_PEN_PRIMARY = 0.25p 

716GRID_CROSS_SIZE_SECONDARY = 0i 

717GRID_PEN_SECONDARY = 0.5p 

718MAP_SCALE_HEIGHT = 0.075i 

719POLAR_CAP = 85/90 

720TICK_LENGTH = 0.075i 

721TICK_PEN = 0.5p 

722X_AXIS_LENGTH = 9i 

723Y_AXIS_LENGTH = 6i 

724X_ORIGIN = 1i 

725Y_ORIGIN = 1i 

726UNIX_TIME = FALSE 

727UNIX_TIME_POS = BL/-0.75i/-0.75i 

728UNIX_TIME_FORMAT = %Y %b %d %H:%M:%S 

729#-------- Color System Parameters ----------- 

730COLOR_BACKGROUND = 0/0/0 

731COLOR_FOREGROUND = 255/255/255 

732COLOR_NAN = 128/128/128 

733COLOR_IMAGE = adobe 

734COLOR_MODEL = rgb 

735HSV_MIN_SATURATION = 1 

736HSV_MAX_SATURATION = 0.1 

737HSV_MIN_VALUE = 0.3 

738HSV_MAX_VALUE = 1 

739#-------- PostScript Parameters ------------- 

740CHAR_ENCODING = ISOLatin1+ 

741DOTS_PR_INCH = 300 

742N_COPIES = 1 

743PS_COLOR = rgb 

744PS_IMAGE_COMPRESS = lzw 

745PS_IMAGE_FORMAT = ascii 

746PS_LINE_CAP = round 

747PS_LINE_JOIN = miter 

748PS_MITER_LIMIT = 35 

749PS_VERBOSE = FALSE 

750GLOBAL_X_SCALE = 1 

751GLOBAL_Y_SCALE = 1 

752#-------- I/O Format Parameters ------------- 

753D_FORMAT = %lg 

754FIELD_DELIMITER = tab 

755GRIDFILE_SHORTHAND = FALSE 

756GRID_FORMAT = nf 

757INPUT_CLOCK_FORMAT = hh:mm:ss 

758INPUT_DATE_FORMAT = yyyy-mm-dd 

759IO_HEADER = FALSE 

760N_HEADER_RECS = 1 

761OUTPUT_CLOCK_FORMAT = hh:mm:ss 

762OUTPUT_DATE_FORMAT = yyyy-mm-dd 

763OUTPUT_DEGREE_FORMAT = +D 

764XY_TOGGLE = FALSE 

765#-------- Projection Parameters ------------- 

766ELLIPSOID = WGS-84 

767MAP_SCALE_FACTOR = default 

768MEASURE_UNIT = inch 

769#-------- Calendar/Time Parameters ---------- 

770TIME_FORMAT_PRIMARY = full 

771TIME_FORMAT_SECONDARY = full 

772TIME_EPOCH = 2000-01-01T00:00:00 

773TIME_IS_INTERVAL = OFF 

774TIME_INTERVAL_FRACTION = 0.5 

775TIME_LANGUAGE = us 

776TIME_UNIT = d 

777TIME_WEEK_START = Sunday 

778Y2K_OFFSET_YEAR = 1950 

779#-------- Miscellaneous Parameters ---------- 

780HISTORY = TRUE 

781INTERPOLANT = akima 

782LINE_STEP = 0.01i 

783VECTOR_SHAPE = 0 

784VERBOSE = FALSE 

785''' 

786 

787_gmt_defaults_by_version['4.5.2'] = r''' 

788# 

789# GMT-SYSTEM 4.5.2 [64-bit] Defaults file 

790# 

791#-------- Plot Media Parameters ------------- 

792PAGE_COLOR = white 

793PAGE_ORIENTATION = portrait 

794PAPER_MEDIA = a4+ 

795#-------- Basemap Annotation Parameters ------ 

796ANNOT_MIN_ANGLE = 20 

797ANNOT_MIN_SPACING = 0 

798ANNOT_FONT_PRIMARY = Helvetica 

799ANNOT_FONT_SIZE_PRIMARY = 14p 

800ANNOT_OFFSET_PRIMARY = 0.075i 

801ANNOT_FONT_SECONDARY = Helvetica 

802ANNOT_FONT_SIZE_SECONDARY = 16p 

803ANNOT_OFFSET_SECONDARY = 0.075i 

804DEGREE_SYMBOL = ring 

805HEADER_FONT = Helvetica 

806HEADER_FONT_SIZE = 36p 

807HEADER_OFFSET = 0.1875i 

808LABEL_FONT = Helvetica 

809LABEL_FONT_SIZE = 14p 

810LABEL_OFFSET = 0.1125i 

811OBLIQUE_ANNOTATION = 1 

812PLOT_CLOCK_FORMAT = hh:mm:ss 

813PLOT_DATE_FORMAT = yyyy-mm-dd 

814PLOT_DEGREE_FORMAT = +ddd:mm:ss 

815Y_AXIS_TYPE = hor_text 

816#-------- Basemap Layout Parameters --------- 

817BASEMAP_AXES = WESN 

818BASEMAP_FRAME_RGB = black 

819BASEMAP_TYPE = plain 

820FRAME_PEN = 1.25p 

821FRAME_WIDTH = 0.075i 

822GRID_CROSS_SIZE_PRIMARY = 0i 

823GRID_PEN_PRIMARY = 0.25p 

824GRID_CROSS_SIZE_SECONDARY = 0i 

825GRID_PEN_SECONDARY = 0.5p 

826MAP_SCALE_HEIGHT = 0.075i 

827POLAR_CAP = 85/90 

828TICK_LENGTH = 0.075i 

829TICK_PEN = 0.5p 

830X_AXIS_LENGTH = 9i 

831Y_AXIS_LENGTH = 6i 

832X_ORIGIN = 1i 

833Y_ORIGIN = 1i 

834UNIX_TIME = FALSE 

835UNIX_TIME_POS = BL/-0.75i/-0.75i 

836UNIX_TIME_FORMAT = %Y %b %d %H:%M:%S 

837#-------- Color System Parameters ----------- 

838COLOR_BACKGROUND = black 

839COLOR_FOREGROUND = white 

840COLOR_NAN = 128 

841COLOR_IMAGE = adobe 

842COLOR_MODEL = rgb 

843HSV_MIN_SATURATION = 1 

844HSV_MAX_SATURATION = 0.1 

845HSV_MIN_VALUE = 0.3 

846HSV_MAX_VALUE = 1 

847#-------- PostScript Parameters ------------- 

848CHAR_ENCODING = ISOLatin1+ 

849DOTS_PR_INCH = 300 

850GLOBAL_X_SCALE = 1 

851GLOBAL_Y_SCALE = 1 

852N_COPIES = 1 

853PS_COLOR = rgb 

854PS_IMAGE_COMPRESS = lzw 

855PS_IMAGE_FORMAT = ascii 

856PS_LINE_CAP = round 

857PS_LINE_JOIN = miter 

858PS_MITER_LIMIT = 35 

859PS_VERBOSE = FALSE 

860TRANSPARENCY = 0 

861#-------- I/O Format Parameters ------------- 

862D_FORMAT = %.12lg 

863FIELD_DELIMITER = tab 

864GRIDFILE_FORMAT = nf 

865GRIDFILE_SHORTHAND = FALSE 

866INPUT_CLOCK_FORMAT = hh:mm:ss 

867INPUT_DATE_FORMAT = yyyy-mm-dd 

868IO_HEADER = FALSE 

869N_HEADER_RECS = 1 

870NAN_RECORDS = pass 

871OUTPUT_CLOCK_FORMAT = hh:mm:ss 

872OUTPUT_DATE_FORMAT = yyyy-mm-dd 

873OUTPUT_DEGREE_FORMAT = D 

874XY_TOGGLE = FALSE 

875#-------- Projection Parameters ------------- 

876ELLIPSOID = WGS-84 

877MAP_SCALE_FACTOR = default 

878MEASURE_UNIT = inch 

879#-------- Calendar/Time Parameters ---------- 

880TIME_FORMAT_PRIMARY = full 

881TIME_FORMAT_SECONDARY = full 

882TIME_EPOCH = 2000-01-01T00:00:00 

883TIME_IS_INTERVAL = OFF 

884TIME_INTERVAL_FRACTION = 0.5 

885TIME_LANGUAGE = us 

886TIME_UNIT = d 

887TIME_WEEK_START = Sunday 

888Y2K_OFFSET_YEAR = 1950 

889#-------- Miscellaneous Parameters ---------- 

890HISTORY = TRUE 

891INTERPOLANT = akima 

892LINE_STEP = 0.01i 

893VECTOR_SHAPE = 0 

894VERBOSE = FALSE 

895''' 

896 

897_gmt_defaults_by_version['4.5.3'] = r''' 

898# 

899# GMT-SYSTEM 4.5.3 (CVS Jun 18 2010 10:56:07) [64-bit] Defaults file 

900# 

901#-------- Plot Media Parameters ------------- 

902PAGE_COLOR = white 

903PAGE_ORIENTATION = portrait 

904PAPER_MEDIA = a4+ 

905#-------- Basemap Annotation Parameters ------ 

906ANNOT_MIN_ANGLE = 20 

907ANNOT_MIN_SPACING = 0 

908ANNOT_FONT_PRIMARY = Helvetica 

909ANNOT_FONT_SIZE_PRIMARY = 14p 

910ANNOT_OFFSET_PRIMARY = 0.075i 

911ANNOT_FONT_SECONDARY = Helvetica 

912ANNOT_FONT_SIZE_SECONDARY = 16p 

913ANNOT_OFFSET_SECONDARY = 0.075i 

914DEGREE_SYMBOL = ring 

915HEADER_FONT = Helvetica 

916HEADER_FONT_SIZE = 36p 

917HEADER_OFFSET = 0.1875i 

918LABEL_FONT = Helvetica 

919LABEL_FONT_SIZE = 14p 

920LABEL_OFFSET = 0.1125i 

921OBLIQUE_ANNOTATION = 1 

922PLOT_CLOCK_FORMAT = hh:mm:ss 

923PLOT_DATE_FORMAT = yyyy-mm-dd 

924PLOT_DEGREE_FORMAT = +ddd:mm:ss 

925Y_AXIS_TYPE = hor_text 

926#-------- Basemap Layout Parameters --------- 

927BASEMAP_AXES = WESN 

928BASEMAP_FRAME_RGB = black 

929BASEMAP_TYPE = plain 

930FRAME_PEN = 1.25p 

931FRAME_WIDTH = 0.075i 

932GRID_CROSS_SIZE_PRIMARY = 0i 

933GRID_PEN_PRIMARY = 0.25p 

934GRID_CROSS_SIZE_SECONDARY = 0i 

935GRID_PEN_SECONDARY = 0.5p 

936MAP_SCALE_HEIGHT = 0.075i 

937POLAR_CAP = 85/90 

938TICK_LENGTH = 0.075i 

939TICK_PEN = 0.5p 

940X_AXIS_LENGTH = 9i 

941Y_AXIS_LENGTH = 6i 

942X_ORIGIN = 1i 

943Y_ORIGIN = 1i 

944UNIX_TIME = FALSE 

945UNIX_TIME_POS = BL/-0.75i/-0.75i 

946UNIX_TIME_FORMAT = %Y %b %d %H:%M:%S 

947#-------- Color System Parameters ----------- 

948COLOR_BACKGROUND = black 

949COLOR_FOREGROUND = white 

950COLOR_NAN = 128 

951COLOR_IMAGE = adobe 

952COLOR_MODEL = rgb 

953HSV_MIN_SATURATION = 1 

954HSV_MAX_SATURATION = 0.1 

955HSV_MIN_VALUE = 0.3 

956HSV_MAX_VALUE = 1 

957#-------- PostScript Parameters ------------- 

958CHAR_ENCODING = ISOLatin1+ 

959DOTS_PR_INCH = 300 

960GLOBAL_X_SCALE = 1 

961GLOBAL_Y_SCALE = 1 

962N_COPIES = 1 

963PS_COLOR = rgb 

964PS_IMAGE_COMPRESS = lzw 

965PS_IMAGE_FORMAT = ascii 

966PS_LINE_CAP = round 

967PS_LINE_JOIN = miter 

968PS_MITER_LIMIT = 35 

969PS_VERBOSE = FALSE 

970TRANSPARENCY = 0 

971#-------- I/O Format Parameters ------------- 

972D_FORMAT = %.12lg 

973FIELD_DELIMITER = tab 

974GRIDFILE_FORMAT = nf 

975GRIDFILE_SHORTHAND = FALSE 

976INPUT_CLOCK_FORMAT = hh:mm:ss 

977INPUT_DATE_FORMAT = yyyy-mm-dd 

978IO_HEADER = FALSE 

979N_HEADER_RECS = 1 

980NAN_RECORDS = pass 

981OUTPUT_CLOCK_FORMAT = hh:mm:ss 

982OUTPUT_DATE_FORMAT = yyyy-mm-dd 

983OUTPUT_DEGREE_FORMAT = D 

984XY_TOGGLE = FALSE 

985#-------- Projection Parameters ------------- 

986ELLIPSOID = WGS-84 

987MAP_SCALE_FACTOR = default 

988MEASURE_UNIT = inch 

989#-------- Calendar/Time Parameters ---------- 

990TIME_FORMAT_PRIMARY = full 

991TIME_FORMAT_SECONDARY = full 

992TIME_EPOCH = 2000-01-01T00:00:00 

993TIME_IS_INTERVAL = OFF 

994TIME_INTERVAL_FRACTION = 0.5 

995TIME_LANGUAGE = us 

996TIME_UNIT = d 

997TIME_WEEK_START = Sunday 

998Y2K_OFFSET_YEAR = 1950 

999#-------- Miscellaneous Parameters ---------- 

1000HISTORY = TRUE 

1001INTERPOLANT = akima 

1002LINE_STEP = 0.01i 

1003VECTOR_SHAPE = 0 

1004VERBOSE = FALSE 

1005''' 

1006 

1007_gmt_defaults_by_version['5.1.2'] = r''' 

1008# 

1009# GMT 5.1.2 Defaults file 

1010# vim:sw=8:ts=8:sts=8 

1011# $Revision: 13836 $ 

1012# $LastChangedDate: 2014-12-20 03:45:42 -1000 (Sat, 20 Dec 2014) $ 

1013# 

1014# COLOR Parameters 

1015# 

1016COLOR_BACKGROUND = black 

1017COLOR_FOREGROUND = white 

1018COLOR_NAN = 127.5 

1019COLOR_MODEL = none 

1020COLOR_HSV_MIN_S = 1 

1021COLOR_HSV_MAX_S = 0.1 

1022COLOR_HSV_MIN_V = 0.3 

1023COLOR_HSV_MAX_V = 1 

1024# 

1025# DIR Parameters 

1026# 

1027DIR_DATA = 

1028DIR_DCW = 

1029DIR_GSHHG = 

1030# 

1031# FONT Parameters 

1032# 

1033FONT_ANNOT_PRIMARY = 14p,Helvetica,black 

1034FONT_ANNOT_SECONDARY = 16p,Helvetica,black 

1035FONT_LABEL = 14p,Helvetica,black 

1036FONT_LOGO = 8p,Helvetica,black 

1037FONT_TITLE = 24p,Helvetica,black 

1038# 

1039# FORMAT Parameters 

1040# 

1041FORMAT_CLOCK_IN = hh:mm:ss 

1042FORMAT_CLOCK_OUT = hh:mm:ss 

1043FORMAT_CLOCK_MAP = hh:mm:ss 

1044FORMAT_DATE_IN = yyyy-mm-dd 

1045FORMAT_DATE_OUT = yyyy-mm-dd 

1046FORMAT_DATE_MAP = yyyy-mm-dd 

1047FORMAT_GEO_OUT = D 

1048FORMAT_GEO_MAP = ddd:mm:ss 

1049FORMAT_FLOAT_OUT = %.12g 

1050FORMAT_FLOAT_MAP = %.12g 

1051FORMAT_TIME_PRIMARY_MAP = full 

1052FORMAT_TIME_SECONDARY_MAP = full 

1053FORMAT_TIME_STAMP = %Y %b %d %H:%M:%S 

1054# 

1055# GMT Miscellaneous Parameters 

1056# 

1057GMT_COMPATIBILITY = 4 

1058GMT_CUSTOM_LIBS = 

1059GMT_EXTRAPOLATE_VAL = NaN 

1060GMT_FFT = auto 

1061GMT_HISTORY = true 

1062GMT_INTERPOLANT = akima 

1063GMT_TRIANGULATE = Shewchuk 

1064GMT_VERBOSE = compat 

1065GMT_LANGUAGE = us 

1066# 

1067# I/O Parameters 

1068# 

1069IO_COL_SEPARATOR = tab 

1070IO_GRIDFILE_FORMAT = nf 

1071IO_GRIDFILE_SHORTHAND = false 

1072IO_HEADER = false 

1073IO_N_HEADER_RECS = 0 

1074IO_NAN_RECORDS = pass 

1075IO_NC4_CHUNK_SIZE = auto 

1076IO_NC4_DEFLATION_LEVEL = 3 

1077IO_LONLAT_TOGGLE = false 

1078IO_SEGMENT_MARKER = > 

1079# 

1080# MAP Parameters 

1081# 

1082MAP_ANNOT_MIN_ANGLE = 20 

1083MAP_ANNOT_MIN_SPACING = 0p 

1084MAP_ANNOT_OBLIQUE = 1 

1085MAP_ANNOT_OFFSET_PRIMARY = 0.075i 

1086MAP_ANNOT_OFFSET_SECONDARY = 0.075i 

1087MAP_ANNOT_ORTHO = we 

1088MAP_DEFAULT_PEN = default,black 

1089MAP_DEGREE_SYMBOL = ring 

1090MAP_FRAME_AXES = WESNZ 

1091MAP_FRAME_PEN = thicker,black 

1092MAP_FRAME_TYPE = fancy 

1093MAP_FRAME_WIDTH = 5p 

1094MAP_GRID_CROSS_SIZE_PRIMARY = 0p 

1095MAP_GRID_CROSS_SIZE_SECONDARY = 0p 

1096MAP_GRID_PEN_PRIMARY = default,black 

1097MAP_GRID_PEN_SECONDARY = thinner,black 

1098MAP_LABEL_OFFSET = 0.1944i 

1099MAP_LINE_STEP = 0.75p 

1100MAP_LOGO = false 

1101MAP_LOGO_POS = BL/-54p/-54p 

1102MAP_ORIGIN_X = 1i 

1103MAP_ORIGIN_Y = 1i 

1104MAP_POLAR_CAP = 85/90 

1105MAP_SCALE_HEIGHT = 5p 

1106MAP_TICK_LENGTH_PRIMARY = 5p/2.5p 

1107MAP_TICK_LENGTH_SECONDARY = 15p/3.75p 

1108MAP_TICK_PEN_PRIMARY = thinner,black 

1109MAP_TICK_PEN_SECONDARY = thinner,black 

1110MAP_TITLE_OFFSET = 14p 

1111MAP_VECTOR_SHAPE = 0 

1112# 

1113# Projection Parameters 

1114# 

1115PROJ_AUX_LATITUDE = authalic 

1116PROJ_ELLIPSOID = WGS-84 

1117PROJ_LENGTH_UNIT = cm 

1118PROJ_MEAN_RADIUS = authalic 

1119PROJ_SCALE_FACTOR = default 

1120# 

1121# PostScript Parameters 

1122# 

1123PS_CHAR_ENCODING = ISOLatin1+ 

1124PS_COLOR_MODEL = rgb 

1125PS_COMMENTS = false 

1126PS_IMAGE_COMPRESS = deflate,5 

1127PS_LINE_CAP = butt 

1128PS_LINE_JOIN = miter 

1129PS_MITER_LIMIT = 35 

1130PS_MEDIA = a4 

1131PS_PAGE_COLOR = white 

1132PS_PAGE_ORIENTATION = portrait 

1133PS_SCALE_X = 1 

1134PS_SCALE_Y = 1 

1135PS_TRANSPARENCY = Normal 

1136# 

1137# Calendar/Time Parameters 

1138# 

1139TIME_EPOCH = 1970-01-01T00:00:00 

1140TIME_IS_INTERVAL = off 

1141TIME_INTERVAL_FRACTION = 0.5 

1142TIME_UNIT = s 

1143TIME_WEEK_START = Monday 

1144TIME_Y2K_OFFSET_YEAR = 1950 

1145''' 

1146 

1147 

1148def get_gmt_version(gmtdefaultsbinary, gmthomedir=None): 

1149 args = [gmtdefaultsbinary] 

1150 

1151 environ = os.environ.copy() 

1152 environ['GMTHOME'] = gmthomedir or '' 

1153 

1154 p = subprocess.Popen( 

1155 args, 

1156 stdout=subprocess.PIPE, 

1157 stderr=subprocess.PIPE, 

1158 env=environ) 

1159 

1160 (stdout, stderr) = p.communicate() 

1161 m = re.search(br'(\d+(\.\d+)*)', stderr) \ 

1162 or re.search(br'# GMT (\d+(\.\d+)*)', stdout) 

1163 

1164 if not m: 

1165 raise GMTInstallationProblem( 

1166 "Can't extract version number from output of %s." 

1167 % gmtdefaultsbinary) 

1168 

1169 return str(m.group(1).decode('ascii')) 

1170 

1171 

1172def detect_gmt_installations(): 

1173 

1174 installations = {} 

1175 errmesses = [] 

1176 

1177 # GMT 4.x: 

1178 try: 

1179 p = subprocess.Popen( 

1180 ['GMT'], 

1181 stdout=subprocess.PIPE, 

1182 stderr=subprocess.PIPE) 

1183 

1184 (stdout, stderr) = p.communicate() 

1185 

1186 m = re.search(br'Version\s+(\d+(\.\d+)*)', stderr, re.M) 

1187 if not m: 

1188 raise GMTInstallationProblem( 

1189 "Can't get version number from output of GMT.") 

1190 

1191 version = str(m.group(1).decode('ascii')) 

1192 if version[0] != '5': 

1193 

1194 m = re.search(br'^\s+executables\s+(.+)$', stderr, re.M) 

1195 if not m: 

1196 raise GMTInstallationProblem( 

1197 "Can't extract executables dir from output of GMT.") 

1198 

1199 gmtbin = str(m.group(1).decode('ascii')) 

1200 

1201 m = re.search(br'^\s+shared data\s+(.+)$', stderr, re.M) 

1202 if not m: 

1203 raise GMTInstallationProblem( 

1204 "Can't extract shared dir from output of GMT.") 

1205 

1206 gmtshare = str(m.group(1).decode('ascii')) 

1207 if not gmtshare.endswith('/share'): 

1208 raise GMTInstallationProblem( 

1209 "Can't determine GMTHOME from output of GMT.") 

1210 

1211 gmthome = gmtshare[:-6] 

1212 

1213 installations[version] = { 

1214 'home': gmthome, 

1215 'bin': gmtbin} 

1216 

1217 except OSError as e: 

1218 errmesses.append(('GMT', str(e))) 

1219 

1220 try: 

1221 version = str(subprocess.check_output( 

1222 ['gmt', '--version']).strip().decode('ascii')).split('_')[0] 

1223 gmtbin = str(subprocess.check_output( 

1224 ['gmt', '--show-bindir']).strip().decode('ascii')) 

1225 installations[version] = { 

1226 'bin': gmtbin} 

1227 

1228 except (OSError, subprocess.CalledProcessError) as e: 

1229 errmesses.append(('gmt', str(e))) 

1230 

1231 if not installations: 

1232 s = [] 

1233 for (progname, errmess) in errmesses: 

1234 s.append('Cannot start "%s" executable: %s' % (progname, errmess)) 

1235 

1236 raise GMTInstallationProblem(', '.join(s)) 

1237 

1238 return installations 

1239 

1240 

1241def appropriate_defaults_version(version): 

1242 avails = sorted(_gmt_defaults_by_version.keys(), key=key_version) 

1243 for iavail, avail in enumerate(avails): 

1244 if key_version(version) == key_version(avail): 

1245 return version 

1246 

1247 elif key_version(version) < key_version(avail): 

1248 return avails[max(0, iavail-1)] 

1249 

1250 return avails[-1] 

1251 

1252 

1253def gmt_default_config(version): 

1254 ''' 

1255 Get default GMT configuration dict for given version. 

1256 ''' 

1257 

1258 xversion = appropriate_defaults_version(version) 

1259 

1260 # if not version in _gmt_defaults_by_version: 

1261 # raise GMTError('No GMT defaults for version %s found' % version) 

1262 

1263 gmt_defaults = _gmt_defaults_by_version[xversion] 

1264 

1265 d = {} 

1266 for line in gmt_defaults.splitlines(): 

1267 sline = line.strip() 

1268 if not sline or sline.startswith('#'): 

1269 continue 

1270 

1271 k, v = sline.split('=', 1) 

1272 d[k.strip()] = v.strip() 

1273 

1274 return d 

1275 

1276 

1277def diff_defaults(v1, v2): 

1278 d1 = gmt_default_config(v1) 

1279 d2 = gmt_default_config(v2) 

1280 for k in d1: 

1281 if k not in d2: 

1282 print('%s not in %s' % (k, v2)) 

1283 else: 

1284 if d1[k] != d2[k]: 

1285 print('%s %s = %s' % (v1, k, d1[k])) 

1286 print('%s %s = %s' % (v2, k, d2[k])) 

1287 

1288 for k in d2: 

1289 if k not in d1: 

1290 print('%s not in %s' % (k, v1)) 

1291 

1292# diff_defaults('4.5.2', '4.5.3') 

1293 

1294 

1295def check_gmt_installation(installation): 

1296 

1297 home_dir = installation.get('home', None) 

1298 bin_dir = installation['bin'] 

1299 version = installation['version'] 

1300 

1301 for d in home_dir, bin_dir: 

1302 if d is not None: 

1303 if not os.path.exists(d): 

1304 logging.error(('Directory does not exist: %s\n' 

1305 'Check your GMT installation.') % d) 

1306 

1307 major_version = version.split('.')[0] 

1308 

1309 if major_version not in ['5', '6']: 

1310 gmtdefaults = pjoin(bin_dir, 'gmtdefaults') 

1311 

1312 versionfound = get_gmt_version(gmtdefaults, home_dir) 

1313 

1314 if versionfound != version: 

1315 raise GMTInstallationProblem(( 

1316 'Expected GMT version %s but found version %s.\n' 

1317 '(Looking at output of %s)') % ( 

1318 version, versionfound, gmtdefaults)) 

1319 

1320 

1321def get_gmt_installation(version): 

1322 setup_gmt_installations() 

1323 if version != 'newest' and version not in _gmt_installations: 

1324 logging.warn('GMT version %s not installed, taking version %s instead' 

1325 % (version, newest_installed_gmt_version())) 

1326 

1327 version = 'newest' 

1328 

1329 if version == 'newest': 

1330 version = newest_installed_gmt_version() 

1331 

1332 installation = dict(_gmt_installations[version]) 

1333 

1334 return installation 

1335 

1336 

1337def setup_gmt_installations(): 

1338 if not setup_gmt_installations.have_done: 

1339 if not _gmt_installations: 

1340 

1341 _gmt_installations.update(detect_gmt_installations()) 

1342 

1343 # store defaults as dicts into the gmt installations dicts 

1344 for version, installation in _gmt_installations.items(): 

1345 installation['defaults'] = gmt_default_config(version) 

1346 installation['version'] = version 

1347 

1348 for installation in _gmt_installations.values(): 

1349 check_gmt_installation(installation) 

1350 

1351 setup_gmt_installations.have_done = True 

1352 

1353 

1354setup_gmt_installations.have_done = False 

1355 

1356_paper_sizes_a = '''A0 2380 3368 

1357 A1 1684 2380 

1358 A2 1190 1684 

1359 A3 842 1190 

1360 A4 595 842 

1361 A5 421 595 

1362 A6 297 421 

1363 A7 210 297 

1364 A8 148 210 

1365 A9 105 148 

1366 A10 74 105 

1367 B0 2836 4008 

1368 B1 2004 2836 

1369 B2 1418 2004 

1370 B3 1002 1418 

1371 B4 709 1002 

1372 B5 501 709 

1373 archA 648 864 

1374 archB 864 1296 

1375 archC 1296 1728 

1376 archD 1728 2592 

1377 archE 2592 3456 

1378 flsa 612 936 

1379 halfletter 396 612 

1380 note 540 720 

1381 letter 612 792 

1382 legal 612 1008 

1383 11x17 792 1224 

1384 ledger 1224 792''' 

1385 

1386 

1387_paper_sizes = {} 

1388 

1389 

1390def setup_paper_sizes(): 

1391 if not _paper_sizes: 

1392 for line in _paper_sizes_a.splitlines(): 

1393 k, w, h = line.split() 

1394 _paper_sizes[k.lower()] = float(w), float(h) 

1395 

1396 

1397def get_paper_size(k): 

1398 setup_paper_sizes() 

1399 return _paper_sizes[k.lower().rstrip('+')] 

1400 

1401 

1402def all_paper_sizes(): 

1403 setup_paper_sizes() 

1404 return _paper_sizes 

1405 

1406 

1407def measure_unit(gmt_config): 

1408 for k in ['MEASURE_UNIT', 'PROJ_LENGTH_UNIT']: 

1409 if k in gmt_config: 

1410 return gmt_config[k] 

1411 

1412 raise GmtPyError('cannot get measure unit / proj length unit from config') 

1413 

1414 

1415def paper_media(gmt_config): 

1416 for k in ['PAPER_MEDIA', 'PS_MEDIA']: 

1417 if k in gmt_config: 

1418 return gmt_config[k] 

1419 

1420 raise GmtPyError('cannot get paper media from config') 

1421 

1422 

1423def page_orientation(gmt_config): 

1424 for k in ['PAGE_ORIENTATION', 'PS_PAGE_ORIENTATION']: 

1425 if k in gmt_config: 

1426 return gmt_config[k] 

1427 

1428 raise GmtPyError('cannot get paper orientation from config') 

1429 

1430 

1431def make_bbox(width, height, gmt_config, margins=(0.8, 0.8, 0.8, 0.8)): 

1432 

1433 leftmargin, topmargin, rightmargin, bottommargin = margins 

1434 portrait = page_orientation(gmt_config).lower() == 'portrait' 

1435 

1436 paper_size = get_paper_size(paper_media(gmt_config)) 

1437 if not portrait: 

1438 paper_size = paper_size[1], paper_size[0] 

1439 

1440 xoffset = (paper_size[0] - (width + leftmargin + rightmargin)) / \ 

1441 2.0 + leftmargin 

1442 yoffset = (paper_size[1] - (height + topmargin + bottommargin)) / \ 

1443 2.0 + bottommargin 

1444 

1445 if portrait: 

1446 bb1 = int((xoffset - leftmargin)) 

1447 bb2 = int((yoffset - bottommargin)) 

1448 bb3 = bb1 + int((width+leftmargin+rightmargin)) 

1449 bb4 = bb2 + int((height+topmargin+bottommargin)) 

1450 else: 

1451 bb1 = int((yoffset - topmargin)) 

1452 bb2 = int((xoffset - leftmargin)) 

1453 bb3 = bb1 + int((height+topmargin+bottommargin)) 

1454 bb4 = bb2 + int((width+leftmargin+rightmargin)) 

1455 

1456 return xoffset, yoffset, (bb1, bb2, bb3, bb4) 

1457 

1458 

1459def gmtdefaults_as_text(version='newest'): 

1460 

1461 ''' 

1462 Get the built-in gmtdefaults. 

1463 ''' 

1464 

1465 if version not in _gmt_installations: 

1466 logging.warn('GMT version %s not installed, taking version %s instead' 

1467 % (version, newest_installed_gmt_version())) 

1468 version = 'newest' 

1469 

1470 if version == 'newest': 

1471 version = newest_installed_gmt_version() 

1472 

1473 return _gmt_defaults_by_version[version] 

1474 

1475 

1476def savegrd(x, y, z, filename, title=None, naming='xy'): 

1477 ''' 

1478 Write COARDS compliant netcdf (grd) file. 

1479 ''' 

1480 

1481 assert y.size, x.size == z.shape 

1482 ny, nx = z.shape 

1483 nc = netcdf_file(filename, 'w') 

1484 assert naming in ('xy', 'lonlat') 

1485 

1486 if naming == 'xy': 

1487 kx, ky = 'x', 'y' 

1488 else: 

1489 kx, ky = 'lon', 'lat' 

1490 

1491 nc.node_offset = 0 

1492 if title is not None: 

1493 nc.title = title 

1494 

1495 nc.Conventions = 'COARDS/CF-1.0' 

1496 nc.createDimension(kx, nx) 

1497 nc.createDimension(ky, ny) 

1498 

1499 xvar = nc.createVariable(kx, 'd', (kx,)) 

1500 yvar = nc.createVariable(ky, 'd', (ky,)) 

1501 if naming == 'xy': 

1502 xvar.long_name = kx 

1503 yvar.long_name = ky 

1504 else: 

1505 xvar.long_name = 'longitude' 

1506 xvar.units = 'degrees_east' 

1507 yvar.long_name = 'latitude' 

1508 yvar.units = 'degrees_north' 

1509 

1510 zvar = nc.createVariable('z', 'd', (ky, kx)) 

1511 

1512 xvar[:] = x.astype(num.float64) 

1513 yvar[:] = y.astype(num.float64) 

1514 zvar[:] = z.astype(num.float64) 

1515 

1516 nc.close() 

1517 

1518 

1519def to_array(var): 

1520 arr = var[:].copy() 

1521 if hasattr(var, 'scale_factor'): 

1522 arr = arr * var.scale_factor 

1523 

1524 if hasattr(var, 'add_offset'): 

1525 arr = arr + var.add_offset 

1526 

1527 return arr 

1528 

1529 

1530def loadgrd(filename): 

1531 ''' 

1532 Read COARDS compliant netcdf (grd) file. 

1533 ''' 

1534 

1535 from pyrocko import util 

1536 

1537 nc = netcdf_file(filename, 'r') 

1538 vkeys = list(nc.variables.keys()) 

1539 kx = 'x' 

1540 ky = 'y' 

1541 if 'lon' in vkeys: 

1542 kx = 'lon' 

1543 if 'lat' in vkeys: 

1544 ky = 'lat' 

1545 

1546 kz = 'z' 

1547 if 'altitude' in vkeys: 

1548 kz = 'altitude' 

1549 

1550 try: 

1551 if all(k in vkeys for k in ['x_range', 'y_range', 'spacing']): 

1552 xmin, xmax = to_array(nc.variables['x_range']) 

1553 ymin, ymax = to_array(nc.variables['y_range']) 

1554 xdelta, ydelta = to_array(nc.variables['spacing']) 

1555 x = util.arange2(xmin, xmax, xdelta) 

1556 y = util.arange2(ymin, ymax, ydelta) 

1557 

1558 else: 

1559 x = to_array(nc.variables[kx]) 

1560 y = to_array(nc.variables[ky]) 

1561 

1562 z = to_array(nc.variables[kz]) 

1563 if z.ndim == 1 and 'dimension' in vkeys: 

1564 z = z.reshape((y.size, x.size)) 

1565 

1566 except KeyError as e: 

1567 raise GmtPyError( 

1568 'Variable not found (available: %s): %s' % ( 

1569 ', '.join(vkeys), str(e))) from e 

1570 

1571 nc.close() 

1572 return x, y, z 

1573 

1574 

1575def centers_to_edges(asorted): 

1576 return (asorted[1:] + asorted[:-1])/2. 

1577 

1578 

1579def nvals(asorted): 

1580 eps = (asorted[-1]-asorted[0])/asorted.size 

1581 return num.sum(asorted[1:] - asorted[:-1] >= eps) + 1 

1582 

1583 

1584def guess_vals(asorted): 

1585 eps = (asorted[-1]-asorted[0])/asorted.size 

1586 indis = num.nonzero(asorted[1:] - asorted[:-1] >= eps)[0] 

1587 indis = num.concatenate((num.array([0]), indis+1, 

1588 num.array([asorted.size]))) 

1589 asum = num.zeros(asorted.size+1) 

1590 asum[1:] = num.cumsum(asorted) 

1591 return (asum[indis[1:]] - asum[indis[:-1]]) / (indis[1:]-indis[:-1]) 

1592 

1593 

1594def blockmean(asorted, b): 

1595 indis = num.nonzero(asorted[1:] - asorted[:-1])[0] 

1596 indis = num.concatenate((num.array([0]), indis+1, 

1597 num.array([asorted.size]))) 

1598 bsum = num.zeros(b.size+1) 

1599 bsum[1:] = num.cumsum(b) 

1600 return ( 

1601 asorted[indis[:-1]], 

1602 (bsum[indis[1:]] - bsum[indis[:-1]]) / (indis[1:]-indis[:-1])) 

1603 

1604 

1605def griddata_regular(x, y, z, xvals, yvals): 

1606 nx, ny = xvals.size, yvals.size 

1607 xindi = num.digitize(x, centers_to_edges(xvals)) 

1608 yindi = num.digitize(y, centers_to_edges(yvals)) 

1609 

1610 zindi = yindi*nx+xindi 

1611 order = num.argsort(zindi) 

1612 z = z[order] 

1613 zindi = zindi[order] 

1614 

1615 zindi, z = blockmean(zindi, z) 

1616 znew = num.empty(nx*ny, dtype=float) 

1617 znew[:] = num.nan 

1618 znew[zindi] = z 

1619 return znew.reshape(ny, nx) 

1620 

1621 

1622def guess_field_size(x_sorted, y_sorted, z=None, mode=None): 

1623 critical_fraction = 1./num.e - 0.014*3 

1624 xs = x_sorted 

1625 ys = y_sorted 

1626 nxs, nys = nvals(xs), nvals(ys) 

1627 if mode == 'nonrandom': 

1628 return nxs, nys, 0 

1629 elif xs.size == nxs*nys: 

1630 # exact match 

1631 return nxs, nys, 0 

1632 elif nxs >= xs.size*critical_fraction and nys >= xs.size*critical_fraction: 

1633 # possibly randomly sampled 

1634 nxs = int(math.sqrt(xs.size)) 

1635 nys = nxs 

1636 return nxs, nys, 2 

1637 else: 

1638 return nxs, nys, 1 

1639 

1640 

1641def griddata_auto(x, y, z, mode=None): 

1642 ''' 

1643 Grid tabular XYZ data by binning. 

1644 

1645 This function does some extra work to guess the size of the grid. This 

1646 should work fine if the input values are already defined on an rectilinear 

1647 grid, even if data points are missing or duplicated. This routine also 

1648 tries to detect a random distribution of input data and in that case 

1649 creates a grid of size sqrt(N) x sqrt(N). 

1650 

1651 The points do not have to be given in any particular order. Grid nodes 

1652 without data are assigned the NaN value. If multiple data points map to the 

1653 same grid node, their average is assigned to the grid node. 

1654 ''' 

1655 

1656 x, y, z = [num.asarray(X) for X in (x, y, z)] 

1657 assert x.size == y.size == z.size 

1658 xs, ys = num.sort(x), num.sort(y) 

1659 nx, ny, badness = guess_field_size(xs, ys, z, mode=mode) 

1660 if badness <= 1: 

1661 xf = guess_vals(xs) 

1662 yf = guess_vals(ys) 

1663 zf = griddata_regular(x, y, z, xf, yf) 

1664 else: 

1665 xf = num.linspace(xs[0], xs[-1], nx) 

1666 yf = num.linspace(ys[0], ys[-1], ny) 

1667 zf = griddata_regular(x, y, z, xf, yf) 

1668 

1669 return xf, yf, zf 

1670 

1671 

1672def tabledata(xf, yf, zf): 

1673 assert yf.size, xf.size == zf.shape 

1674 x = num.tile(xf, yf.size) 

1675 y = num.repeat(yf, xf.size) 

1676 z = zf.flatten() 

1677 return x, y, z 

1678 

1679 

1680def double1d(a): 

1681 a2 = num.empty(a.size*2-1) 

1682 a2[::2] = a 

1683 a2[1::2] = (a[:-1] + a[1:])/2. 

1684 return a2 

1685 

1686 

1687def double2d(f): 

1688 f2 = num.empty((f.shape[0]*2-1, f.shape[1]*2-1)) 

1689 f2[:, :] = num.nan 

1690 f2[::2, ::2] = f 

1691 f2[1::2, ::2] = (f[:-1, :] + f[1:, :])/2. 

1692 f2[::2, 1::2] = (f[:, :-1] + f[:, 1:])/2. 

1693 f2[1::2, 1::2] = (f[:-1, :-1] + f[1:, :-1] + f[:-1, 1:] + f[1:, 1:])/4. 

1694 diag = f2[1::2, 1::2] 

1695 diagA = (f[:-1, :-1] + f[1:, 1:]) / 2. 

1696 diagB = (f[1:, :-1] + f[:-1, 1:]) / 2. 

1697 f2[1::2, 1::2] = num.where(num.isnan(diag), diagA, diag) 

1698 f2[1::2, 1::2] = num.where(num.isnan(diag), diagB, diag) 

1699 return f2 

1700 

1701 

1702def doublegrid(x, y, z): 

1703 x2 = double1d(x) 

1704 y2 = double1d(y) 

1705 z2 = double2d(z) 

1706 return x2, y2, z2 

1707 

1708 

1709class Guru(object): 

1710 ''' 

1711 Abstract base class providing template interpolation, accessible as 

1712 attributes. 

1713 

1714 Classes deriving from this one, have to implement a :py:meth:`get_params` 

1715 method, which is called to get a dict to do ordinary 

1716 ``"%(key)x"``-substitutions. The deriving class must also provide a dict 

1717 with the templates. 

1718 ''' 

1719 

1720 def __init__(self): 

1721 self.templates = {} 

1722 

1723 def get_params(self, ax_projection=False): 

1724 ''' 

1725 To be implemented in subclasses. 

1726 ''' 

1727 raise NotImplementedError 

1728 

1729 def fill(self, templates, **kwargs): 

1730 params = self.get_params(**kwargs) 

1731 strings = [t % params for t in templates] 

1732 return strings 

1733 

1734 # hand through templates dict 

1735 def __getitem__(self, template_name): 

1736 return self.templates[template_name] 

1737 

1738 def __setitem__(self, template_name, template): 

1739 self.templates[template_name] = template 

1740 

1741 def __contains__(self, template_name): 

1742 return template_name in self.templates 

1743 

1744 def __iter__(self): 

1745 return iter(self.templates) 

1746 

1747 def __len__(self): 

1748 return len(self.templates) 

1749 

1750 def __delitem__(self, template_name): 

1751 del self.templates[template_name] 

1752 

1753 def _simple_fill(self, template_names, **kwargs): 

1754 templates = [self.templates[n] for n in template_names] 

1755 return self.fill(templates, **kwargs) 

1756 

1757 def __getattr__(self, template_names): 

1758 if [n for n in template_names if n not in self.templates]: 

1759 raise AttributeError(template_names) 

1760 

1761 def f(**kwargs): 

1762 return self._simple_fill(template_names, **kwargs) 

1763 

1764 return f 

1765 

1766 

1767class Ax(AutoScaler): 

1768 ''' 

1769 Ax description with autoscaling capabilities. 

1770 

1771 The ax is described by the :py:class:`~pyrocko.plot.AutoScaler` 

1772 public attributes, plus the following additional attributes 

1773 (with default values given in paranthesis): 

1774 

1775 .. py:attribute:: label 

1776 

1777 Ax label (without unit). 

1778 

1779 .. py:attribute:: unit 

1780 

1781 Physical unit of the data attached to this ax. 

1782 

1783 .. py:attribute:: scaled_unit 

1784 

1785 (see below) 

1786 

1787 .. py:attribute:: scaled_unit_factor 

1788 

1789 Scaled physical unit and factor between unit and scaled_unit so that 

1790 

1791 unit = scaled_unit_factor x scaled_unit. 

1792 

1793 (E.g. if unit is 'm' and data is in the range of nanometers, you may 

1794 want to set the scaled_unit to 'nm' and the scaled_unit_factor to 

1795 1e9.) 

1796 

1797 .. py:attribute:: limits 

1798 

1799 If defined, fix range of ax to limits=(min,max). 

1800 

1801 .. py:attribute:: masking 

1802 

1803 If true and if there is a limit on the ax, while calculating ranges, 

1804 the data points are masked such that data points outside of this axes 

1805 limits are not used to determine the range of another dependant ax. 

1806 

1807 ''' 

1808 

1809 def __init__(self, label='', unit='', scaled_unit_factor=1., 

1810 scaled_unit='', limits=None, masking=True, **kwargs): 

1811 

1812 AutoScaler.__init__(self, **kwargs) 

1813 self.label = label 

1814 self.unit = unit 

1815 self.scaled_unit_factor = scaled_unit_factor 

1816 self.scaled_unit = scaled_unit 

1817 self.limits = limits 

1818 self.masking = masking 

1819 

1820 def label_str(self, exp, unit): 

1821 ''' 

1822 Get label string including the unit and multiplier. 

1823 ''' 

1824 

1825 slabel, sunit, sexp = '', '', '' 

1826 if self.label: 

1827 slabel = self.label 

1828 

1829 if unit or exp != 0: 

1830 if exp != 0: 

1831 sexp = '\\327 10@+%i@+' % exp 

1832 sunit = '[ %s %s ]' % (sexp, unit) 

1833 else: 

1834 sunit = '[ %s ]' % unit 

1835 

1836 p = [] 

1837 if slabel: 

1838 p.append(slabel) 

1839 

1840 if sunit: 

1841 p.append(sunit) 

1842 

1843 return ' '.join(p) 

1844 

1845 def make_params(self, data_range, ax_projection=False, override_mode=None, 

1846 override_scaled_unit_factor=None): 

1847 

1848 ''' 

1849 Get minimum, maximum, increment and label string for ax display.' 

1850 

1851 Returns minimum, maximum, increment and label string including unit and 

1852 multiplier for given data range. 

1853 

1854 If ``ax_projection`` is True, values suitable to be displayed on the ax 

1855 are returned, e.g. min, max and inc are returned in scaled units. 

1856 Otherwise the values are returned in the original units, without any 

1857 scaling applied. 

1858 ''' 

1859 

1860 sf = self.scaled_unit_factor 

1861 

1862 if override_scaled_unit_factor is not None: 

1863 sf = override_scaled_unit_factor 

1864 

1865 dr_scaled = [sf*x for x in data_range] 

1866 

1867 mi, ma, inc = self.make_scale(dr_scaled, override_mode=override_mode) 

1868 if self.inc is not None: 

1869 inc = self.inc*sf 

1870 

1871 if ax_projection: 

1872 exp = self.make_exp(inc) 

1873 if sf == 1. and override_scaled_unit_factor is None: 

1874 unit = self.unit 

1875 else: 

1876 unit = self.scaled_unit 

1877 label = self.label_str(exp, unit) 

1878 return mi/10**exp, ma/10**exp, inc/10**exp, label 

1879 else: 

1880 label = self.label_str(0, self.unit) 

1881 return mi/sf, ma/sf, inc/sf, label 

1882 

1883 

1884class ScaleGuru(Guru): 

1885 

1886 ''' 

1887 2D/3D autoscaling and ax annotation facility. 

1888 

1889 Instances of this class provide automatic determination of plot ranges, 

1890 tick increments and scaled annotations, as well as label/unit handling. It 

1891 can in particular be used to automatically generate the -R and -B option 

1892 arguments, which are required for most GMT commands. 

1893 

1894 It extends the functionality of the :py:class:`Ax` and 

1895 :py:class:`~pyrocko.plot.AutoScaler` classes at the level, where it can not 

1896 be handled anymore by looking at a single dimension of the dataset's data, 

1897 e.g.: 

1898 

1899 * The ability to impose a fixed aspect ratio between two axes. 

1900 

1901 * Recalculation of data range on non-limited axes, when there are 

1902 limits imposed on other axes. 

1903 

1904 ''' 

1905 

1906 def __init__(self, data_tuples=None, axes=None, aspect=None, 

1907 percent_interval=None, copy_from=None): 

1908 

1909 Guru.__init__(self) 

1910 

1911 if copy_from: 

1912 self.templates = copy.deepcopy(copy_from.templates) 

1913 self.axes = copy.deepcopy(copy_from.axes) 

1914 self.data_ranges = copy.deepcopy(copy_from.data_ranges) 

1915 self.aspect = copy_from.aspect 

1916 

1917 if percent_interval is not None: 

1918 from scipy.stats import scoreatpercentile as scap 

1919 

1920 self.templates = dict( 

1921 R='-R%(xmin)g/%(xmax)g/%(ymin)g/%(ymax)g', 

1922 B='-B%(xinc)g:%(xlabel)s:/%(yinc)g:%(ylabel)s:WSen', 

1923 T='-T%(zmin)g/%(zmax)g/%(zinc)g') 

1924 

1925 maxdim = 2 

1926 if data_tuples: 

1927 maxdim = max(maxdim, max([len(dt) for dt in data_tuples])) 

1928 else: 

1929 if axes: 

1930 maxdim = len(axes) 

1931 data_tuples = [([],) * maxdim] 

1932 if axes is not None: 

1933 self.axes = axes 

1934 else: 

1935 self.axes = [Ax() for i in range(maxdim)] 

1936 

1937 # sophisticated data-range calculation 

1938 data_ranges = [None] * maxdim 

1939 for dt_ in data_tuples: 

1940 dt = num.asarray(dt_) 

1941 in_range = True 

1942 for ax, x in zip(self.axes, dt): 

1943 if ax.limits and ax.masking: 

1944 ax_limits = list(ax.limits) 

1945 if ax_limits[0] is None: 

1946 ax_limits[0] = -num.inf 

1947 if ax_limits[1] is None: 

1948 ax_limits[1] = num.inf 

1949 in_range = num.logical_and( 

1950 in_range, 

1951 num.logical_and(ax_limits[0] <= x, x <= ax_limits[1])) 

1952 

1953 for i, ax, x in zip(range(maxdim), self.axes, dt): 

1954 

1955 if not ax.limits or None in ax.limits: 

1956 if len(x) >= 1: 

1957 if in_range is not True: 

1958 xmasked = num.where(in_range, x, num.NaN) 

1959 if percent_interval is None: 

1960 range_this = ( 

1961 num.nanmin(xmasked), 

1962 num.nanmax(xmasked)) 

1963 else: 

1964 xmasked_finite = num.compress( 

1965 num.isfinite(xmasked), xmasked) 

1966 range_this = ( 

1967 scap(xmasked_finite, 

1968 (100.-percent_interval)/2.), 

1969 scap(xmasked_finite, 

1970 100.-(100.-percent_interval)/2.)) 

1971 else: 

1972 if percent_interval is None: 

1973 range_this = num.nanmin(x), num.nanmax(x) 

1974 else: 

1975 xmasked_finite = num.compress( 

1976 num.isfinite(xmasked), xmasked) 

1977 range_this = ( 

1978 scap(xmasked_finite, 

1979 (100.-percent_interval)/2.), 

1980 scap(xmasked_finite, 

1981 100.-(100.-percent_interval)/2.)) 

1982 else: 

1983 range_this = (0., 1.) 

1984 

1985 if ax.limits: 

1986 if ax.limits[0] is not None: 

1987 range_this = ax.limits[0], max(ax.limits[0], 

1988 range_this[1]) 

1989 

1990 if ax.limits[1] is not None: 

1991 range_this = min(ax.limits[1], 

1992 range_this[0]), ax.limits[1] 

1993 

1994 else: 

1995 range_this = ax.limits 

1996 

1997 if data_ranges[i] is None and range_this[0] <= range_this[1]: 

1998 data_ranges[i] = range_this 

1999 else: 

2000 mi, ma = range_this 

2001 if data_ranges[i] is not None: 

2002 mi = min(data_ranges[i][0], mi) 

2003 ma = max(data_ranges[i][1], ma) 

2004 

2005 data_ranges[i] = (mi, ma) 

2006 

2007 for i in range(len(data_ranges)): 

2008 if data_ranges[i] is None or not ( 

2009 num.isfinite(data_ranges[i][0]) 

2010 and num.isfinite(data_ranges[i][1])): 

2011 

2012 data_ranges[i] = (0., 1.) 

2013 

2014 self.data_ranges = data_ranges 

2015 self.aspect = aspect 

2016 

2017 def copy(self): 

2018 return ScaleGuru(copy_from=self) 

2019 

2020 def get_params(self, ax_projection=False): 

2021 

2022 ''' 

2023 Get dict with output parameters. 

2024 

2025 For each data dimension, ax minimum, maximum, increment and a label 

2026 string (including unit and exponential factor) are determined. E.g. in 

2027 for the first dimension the output dict will contain the keys 

2028 ``'xmin'``, ``'xmax'``, ``'xinc'``, and ``'xlabel'``. 

2029 

2030 Normally, values corresponding to the scaling of the raw data are 

2031 produced, but if ``ax_projection`` is ``True``, values which are 

2032 suitable to be printed on the axes are returned. This means that in the 

2033 latter case, the :py:attr:`Ax.scaled_unit` and 

2034 :py:attr:`Ax.scaled_unit_factor` attributes as set on the axes are 

2035 respected and that a common 10^x factor is factored out and put to the 

2036 label string. 

2037 ''' 

2038 

2039 xmi, xma, xinc, xlabel = self.axes[0].make_params( 

2040 self.data_ranges[0], ax_projection) 

2041 ymi, yma, yinc, ylabel = self.axes[1].make_params( 

2042 self.data_ranges[1], ax_projection) 

2043 if len(self.axes) > 2: 

2044 zmi, zma, zinc, zlabel = self.axes[2].make_params( 

2045 self.data_ranges[2], ax_projection) 

2046 

2047 # enforce certain aspect, if needed 

2048 if self.aspect is not None: 

2049 xwid = xma-xmi 

2050 ywid = yma-ymi 

2051 if ywid < xwid*self.aspect: 

2052 ymi -= (xwid*self.aspect - ywid)*0.5 

2053 yma += (xwid*self.aspect - ywid)*0.5 

2054 ymi, yma, yinc, ylabel = self.axes[1].make_params( 

2055 (ymi, yma), ax_projection, override_mode='off', 

2056 override_scaled_unit_factor=1.) 

2057 

2058 elif xwid < ywid/self.aspect: 

2059 xmi -= (ywid/self.aspect - xwid)*0.5 

2060 xma += (ywid/self.aspect - xwid)*0.5 

2061 xmi, xma, xinc, xlabel = self.axes[0].make_params( 

2062 (xmi, xma), ax_projection, override_mode='off', 

2063 override_scaled_unit_factor=1.) 

2064 

2065 params = dict(xmin=xmi, xmax=xma, xinc=xinc, xlabel=xlabel, 

2066 ymin=ymi, ymax=yma, yinc=yinc, ylabel=ylabel) 

2067 if len(self.axes) > 2: 

2068 params.update(dict(zmin=zmi, zmax=zma, zinc=zinc, zlabel=zlabel)) 

2069 

2070 return params 

2071 

2072 

2073class GumSpring(object): 

2074 

2075 ''' 

2076 Sizing policy implementing a minimal size, plus a desire to grow. 

2077 ''' 

2078 

2079 def __init__(self, minimal=None, grow=None): 

2080 self.minimal = minimal 

2081 if grow is None: 

2082 if minimal is None: 

2083 self.grow = 1.0 

2084 else: 

2085 self.grow = 0.0 

2086 else: 

2087 self.grow = grow 

2088 self.value = 1.0 

2089 

2090 def get_minimal(self): 

2091 if self.minimal is not None: 

2092 return self.minimal 

2093 else: 

2094 return 0.0 

2095 

2096 def get_grow(self): 

2097 return self.grow 

2098 

2099 def set_value(self, value): 

2100 self.value = value 

2101 

2102 def get_value(self): 

2103 return self.value 

2104 

2105 

2106def distribute(sizes, grows, space): 

2107 sizes = list(sizes) 

2108 gsum = sum(grows) 

2109 if gsum > 0.0: 

2110 for i in range(len(sizes)): 

2111 sizes[i] += space*grows[i]/gsum 

2112 return sizes 

2113 

2114 

2115class Widget(Guru): 

2116 

2117 ''' 

2118 Base class of the gmtpy layout system. 

2119 

2120 The Widget class provides the basic functionality for the nesting and 

2121 placing of elements on the output page, and maintains the sizing policies 

2122 of each element. Each of the layouts defined in gmtpy is itself a Widget. 

2123 

2124 Sizing of the widget is controlled by :py:meth:`get_min_size` and 

2125 :py:meth:`get_grow` which should be overloaded in derived classes. The 

2126 basic behaviour of a Widget instance is to have a vertical and a horizontal 

2127 minimum size which default to zero, as well as a vertical and a horizontal 

2128 desire to grow, represented by floats, which default to 1.0. Additionally 

2129 an aspect ratio constraint may be imposed on the Widget. 

2130 

2131 After layouting, the widget provides its width, height, x-offset and 

2132 y-offset in various ways. Via the Guru interface (see :py:class:`Guru` 

2133 class), templates for the -X, -Y and -J option arguments used by GMT 

2134 arguments are provided. The defaults are suitable for plotting of linear 

2135 (-JX) plots. Other projections can be selected by giving an appropriate 'J' 

2136 template, or by manual construction of the -J option, e.g. by utilizing the 

2137 :py:meth:`width` and :py:meth:`height` methods. The :py:meth:`bbox` method 

2138 can be used to create a PostScript bounding box from the widgets border, 

2139 e.g. for use in :py:meth:`GMT.save`. 

2140 

2141 The convention is, that all sizes are given in PostScript points. 

2142 Conversion factors are provided as constants :py:const:`inch` and 

2143 :py:const:`cm` in the gmtpy module. 

2144 ''' 

2145 

2146 def __init__(self, horizontal=None, vertical=None, parent=None): 

2147 

2148 ''' 

2149 Create new widget. 

2150 ''' 

2151 

2152 Guru.__init__(self) 

2153 

2154 self.templates = dict( 

2155 X='-Xa%(xoffset)gp', 

2156 Y='-Ya%(yoffset)gp', 

2157 J='-JX%(width)gp/%(height)gp') 

2158 

2159 if horizontal is None: 

2160 self.horizontal = GumSpring() 

2161 else: 

2162 self.horizontal = horizontal 

2163 

2164 if vertical is None: 

2165 self.vertical = GumSpring() 

2166 else: 

2167 self.vertical = vertical 

2168 

2169 self.aspect = None 

2170 self.parent = parent 

2171 self.dirty = True 

2172 

2173 def set_widget(self): 

2174 ''' 

2175 To be implemented in subclasses. 

2176 ''' 

2177 raise NotImplementedError 

2178 

2179 def set_parent(self, parent): 

2180 

2181 ''' 

2182 Set the parent widget. 

2183 

2184 This method should not be called directly. The :py:meth:`set_widget` 

2185 methods are responsible for calling this. 

2186 ''' 

2187 

2188 self.parent = parent 

2189 self.dirtyfy() 

2190 

2191 def get_parent(self): 

2192 

2193 ''' 

2194 Get the widgets parent widget. 

2195 ''' 

2196 

2197 return self.parent 

2198 

2199 def get_root(self): 

2200 

2201 ''' 

2202 Get the root widget in the layout hierarchy. 

2203 ''' 

2204 

2205 if self.parent is not None: 

2206 return self.get_parent() 

2207 else: 

2208 return self 

2209 

2210 def set_horizontal(self, minimal=None, grow=None): 

2211 

2212 ''' 

2213 Set the horizontal sizing policy of the Widget. 

2214 

2215 

2216 :param minimal: new minimal width of the widget 

2217 :param grow: new horizontal grow disire of the widget 

2218 ''' 

2219 

2220 self.horizontal = GumSpring(minimal, grow) 

2221 self.dirtyfy() 

2222 

2223 def get_horizontal(self): 

2224 return self.horizontal.get_minimal(), self.horizontal.get_grow() 

2225 

2226 def set_vertical(self, minimal=None, grow=None): 

2227 

2228 ''' 

2229 Set the horizontal sizing policy of the Widget. 

2230 

2231 :param minimal: new minimal height of the widget 

2232 :param grow: new vertical grow disire of the widget 

2233 ''' 

2234 

2235 self.vertical = GumSpring(minimal, grow) 

2236 self.dirtyfy() 

2237 

2238 def get_vertical(self): 

2239 return self.vertical.get_minimal(), self.vertical.get_grow() 

2240 

2241 def set_aspect(self, aspect=None): 

2242 

2243 ''' 

2244 Set aspect constraint on the widget. 

2245 

2246 The aspect is given as height divided by width. 

2247 ''' 

2248 

2249 self.aspect = aspect 

2250 self.dirtyfy() 

2251 

2252 def set_policy(self, minimal=(None, None), grow=(None, None), aspect=None): 

2253 

2254 ''' 

2255 Shortcut to set sizing and aspect constraints in a single method 

2256 call. 

2257 ''' 

2258 

2259 self.set_horizontal(minimal[0], grow[0]) 

2260 self.set_vertical(minimal[1], grow[1]) 

2261 self.set_aspect(aspect) 

2262 

2263 def get_policy(self): 

2264 mh, gh = self.get_horizontal() 

2265 mv, gv = self.get_vertical() 

2266 return (mh, mv), (gh, gv), self.aspect 

2267 

2268 def legalize(self, size, offset): 

2269 

2270 ''' 

2271 Get legal size for widget. 

2272 

2273 Returns: (new_size, new_offset) 

2274 

2275 Given a box as ``size`` and ``offset``, return ``new_size`` and 

2276 ``new_offset``, such that the widget's sizing and aspect constraints 

2277 are fullfilled. The returned box is centered on the given input box. 

2278 ''' 

2279 

2280 sh, sv = size 

2281 oh, ov = offset 

2282 shs, svs = Widget.get_min_size(self) 

2283 ghs, gvs = Widget.get_grow(self) 

2284 

2285 if ghs == 0.0: 

2286 oh += (sh-shs)/2. 

2287 sh = shs 

2288 

2289 if gvs == 0.0: 

2290 ov += (sv-svs)/2. 

2291 sv = svs 

2292 

2293 if self.aspect is not None: 

2294 if sh > sv/self.aspect: 

2295 oh += (sh-sv/self.aspect)/2. 

2296 sh = sv/self.aspect 

2297 if sv > sh*self.aspect: 

2298 ov += (sv-sh*self.aspect)/2. 

2299 sv = sh*self.aspect 

2300 

2301 return (sh, sv), (oh, ov) 

2302 

2303 def get_min_size(self): 

2304 

2305 ''' 

2306 Get minimum size of widget. 

2307 

2308 Used by the layout managers. Should be overloaded in derived classes. 

2309 ''' 

2310 

2311 mh, mv = self.horizontal.get_minimal(), self.vertical.get_minimal() 

2312 if self.aspect is not None: 

2313 if mv == 0.0: 

2314 return mh, mh*self.aspect 

2315 elif mh == 0.0: 

2316 return mv/self.aspect, mv 

2317 return mh, mv 

2318 

2319 def get_grow(self): 

2320 

2321 ''' 

2322 Get widget's desire to grow. 

2323 

2324 Used by the layout managers. Should be overloaded in derived classes. 

2325 ''' 

2326 

2327 return self.horizontal.get_grow(), self.vertical.get_grow() 

2328 

2329 def set_size(self, size, offset): 

2330 

2331 ''' 

2332 Set the widget's current size. 

2333 

2334 Should not be called directly. It is the layout manager's 

2335 responsibility to call this. 

2336 ''' 

2337 

2338 (sh, sv), inner_offset = self.legalize(size, offset) 

2339 self.offset = inner_offset 

2340 self.horizontal.set_value(sh) 

2341 self.vertical.set_value(sv) 

2342 self.dirty = False 

2343 

2344 def __str__(self): 

2345 

2346 def indent(ind, str): 

2347 return ('\n'+ind).join(str.splitlines()) 

2348 size, offset = self.get_size() 

2349 s = '%s (%g x %g) (%g, %g)\n' % ((self.__class__,) + size + offset) 

2350 children = self.get_children() 

2351 if children: 

2352 s += '\n'.join([' ' + indent(' ', str(c)) for c in children]) 

2353 return s 

2354 

2355 def policies_debug_str(self): 

2356 

2357 def indent(ind, str): 

2358 return ('\n'+ind).join(str.splitlines()) 

2359 mins, grows, aspect = self.get_policy() 

2360 s = '%s: minimum=(%s, %s), grow=(%s, %s), aspect=%s\n' % ( 

2361 (self.__class__,) + mins+grows+(aspect,)) 

2362 

2363 children = self.get_children() 

2364 if children: 

2365 s += '\n'.join([' ' + indent( 

2366 ' ', c.policies_debug_str()) for c in children]) 

2367 return s 

2368 

2369 def get_corners(self, descend=False): 

2370 

2371 ''' 

2372 Get coordinates of the corners of the widget. 

2373 

2374 Returns list with coordinate tuples. 

2375 

2376 If ``descend`` is True, the returned list will contain corner 

2377 coordinates of all sub-widgets. 

2378 ''' 

2379 

2380 self.do_layout() 

2381 (sh, sv), (oh, ov) = self.get_size() 

2382 corners = [(oh, ov), (oh+sh, ov), (oh+sh, ov+sv), (oh, ov+sv)] 

2383 if descend: 

2384 for child in self.get_children(): 

2385 corners.extend(child.get_corners(descend=True)) 

2386 return corners 

2387 

2388 def get_sizes(self): 

2389 

2390 ''' 

2391 Get sizes of this widget and all it's children. 

2392 

2393 Returns a list with size tuples. 

2394 ''' 

2395 self.do_layout() 

2396 sizes = [self.get_size()] 

2397 for child in self.get_children(): 

2398 sizes.extend(child.get_sizes()) 

2399 return sizes 

2400 

2401 def do_layout(self): 

2402 

2403 ''' 

2404 Triggers layouting of the widget hierarchy, if needed. 

2405 ''' 

2406 

2407 if self.parent is not None: 

2408 return self.parent.do_layout() 

2409 

2410 if not self.dirty: 

2411 return 

2412 

2413 sh, sv = self.get_min_size() 

2414 gh, gv = self.get_grow() 

2415 if sh == 0.0 and gh != 0.0: 

2416 sh = 15.*cm 

2417 if sv == 0.0 and gv != 0.0: 

2418 sv = 15.*cm*gv/gh * 1./golden_ratio 

2419 self.set_size((sh, sv), (0., 0.)) 

2420 

2421 def get_children(self): 

2422 

2423 ''' 

2424 Get sub-widgets contained in this widget. 

2425 

2426 Returns a list of widgets. 

2427 ''' 

2428 

2429 return [] 

2430 

2431 def get_size(self): 

2432 

2433 ''' 

2434 Get current size and position of the widget. 

2435 

2436 Triggers layouting and returns 

2437 ``((width, height), (xoffset, yoffset))`` 

2438 ''' 

2439 

2440 self.do_layout() 

2441 return (self.horizontal.get_value(), 

2442 self.vertical.get_value()), self.offset 

2443 

2444 def get_params(self): 

2445 

2446 ''' 

2447 Get current size and position of the widget. 

2448 

2449 Triggers layouting and returns dict with keys ``'xoffset'``, 

2450 ``'yoffset'``, ``'width'`` and ``'height'``. 

2451 ''' 

2452 

2453 self.do_layout() 

2454 (w, h), (xo, yo) = self.get_size() 

2455 return dict(xoffset=xo, yoffset=yo, width=w, height=h, 

2456 width_m=w/_units['m']) 

2457 

2458 def width(self): 

2459 

2460 ''' 

2461 Get current width of the widget. 

2462 

2463 Triggers layouting and returns width. 

2464 ''' 

2465 

2466 self.do_layout() 

2467 return self.horizontal.get_value() 

2468 

2469 def height(self): 

2470 

2471 ''' 

2472 Get current height of the widget. 

2473 

2474 Triggers layouting and return height. 

2475 ''' 

2476 

2477 self.do_layout() 

2478 return self.vertical.get_value() 

2479 

2480 def bbox(self): 

2481 

2482 ''' 

2483 Get PostScript bounding box for this widget. 

2484 

2485 Triggers layouting and returns values suitable to create PS bounding 

2486 box, representing the widgets current size and position. 

2487 ''' 

2488 

2489 self.do_layout() 

2490 return (self.offset[0], self.offset[1], self.offset[0]+self.width(), 

2491 self.offset[1]+self.height()) 

2492 

2493 def dirtyfy(self): 

2494 

2495 ''' 

2496 Set dirty flag on top level widget in the hierarchy. 

2497 

2498 Called by various methods, to indicate, that the widget hierarchy needs 

2499 new layouting. 

2500 ''' 

2501 

2502 if self.parent is not None: 

2503 self.parent.dirtyfy() 

2504 

2505 self.dirty = True 

2506 

2507 

2508class CenterLayout(Widget): 

2509 

2510 ''' 

2511 A layout manager which centers its single child widget. 

2512 

2513 The child widget may be oversized. 

2514 ''' 

2515 

2516 def __init__(self, horizontal=None, vertical=None): 

2517 Widget.__init__(self, horizontal, vertical) 

2518 self.content = Widget(horizontal=GumSpring(grow=1.), 

2519 vertical=GumSpring(grow=1.), parent=self) 

2520 

2521 def get_min_size(self): 

2522 shs, svs = Widget.get_min_size(self) 

2523 sh, sv = self.content.get_min_size() 

2524 return max(shs, sh), max(svs, sv) 

2525 

2526 def get_grow(self): 

2527 ghs, gvs = Widget.get_grow(self) 

2528 gh, gv = self.content.get_grow() 

2529 return gh*ghs, gv*gvs 

2530 

2531 def set_size(self, size, offset): 

2532 (sh, sv), (oh, ov) = self.legalize(size, offset) 

2533 

2534 shc, svc = self.content.get_min_size() 

2535 ghc, gvc = self.content.get_grow() 

2536 if ghc != 0.: 

2537 shc = sh 

2538 if gvc != 0.: 

2539 svc = sv 

2540 ohc = oh+(sh-shc)/2. 

2541 ovc = ov+(sv-svc)/2. 

2542 

2543 self.content.set_size((shc, svc), (ohc, ovc)) 

2544 Widget.set_size(self, (sh, sv), (oh, ov)) 

2545 

2546 def set_widget(self, widget=None): 

2547 

2548 ''' 

2549 Set the child widget, which shall be centered. 

2550 ''' 

2551 

2552 if widget is None: 

2553 widget = Widget() 

2554 

2555 self.content = widget 

2556 

2557 widget.set_parent(self) 

2558 

2559 def get_widget(self): 

2560 return self.content 

2561 

2562 def get_children(self): 

2563 return [self.content] 

2564 

2565 

2566class FrameLayout(Widget): 

2567 

2568 ''' 

2569 A layout manager containing a center widget sorrounded by four margin 

2570 widgets. 

2571 

2572 :: 

2573 

2574 +---------------------------+ 

2575 | top | 

2576 +---------------------------+ 

2577 | | | | 

2578 | left | center | right | 

2579 | | | | 

2580 +---------------------------+ 

2581 | bottom | 

2582 +---------------------------+ 

2583 

2584 This layout manager does a little bit of extra effort to maintain the 

2585 aspect constraint of the center widget, if this is set. It does so, by 

2586 allowing for a bit more flexibility in the sizing of the margins. Two 

2587 shortcut methods are provided to set the margin sizes in one shot: 

2588 :py:meth:`set_fixed_margins` and :py:meth:`set_min_margins`. The first sets 

2589 the margins to fixed sizes, while the second gives them a minimal size and 

2590 a (neglectably) small desire to grow. Using the latter may be useful when 

2591 setting an aspect constraint on the center widget, because this way the 

2592 maximum size of the center widget may be controlled without creating empty 

2593 spaces between the widgets. 

2594 ''' 

2595 

2596 def __init__(self, horizontal=None, vertical=None): 

2597 Widget.__init__(self, horizontal, vertical) 

2598 mw = 3.*cm 

2599 self.left = Widget( 

2600 horizontal=GumSpring(grow=0.15, minimal=mw), parent=self) 

2601 self.right = Widget( 

2602 horizontal=GumSpring(grow=0.15, minimal=mw), parent=self) 

2603 self.top = Widget( 

2604 vertical=GumSpring(grow=0.15, minimal=mw/golden_ratio), 

2605 parent=self) 

2606 self.bottom = Widget( 

2607 vertical=GumSpring(grow=0.15, minimal=mw/golden_ratio), 

2608 parent=self) 

2609 self.center = Widget( 

2610 horizontal=GumSpring(grow=0.7), vertical=GumSpring(grow=0.7), 

2611 parent=self) 

2612 

2613 def set_fixed_margins(self, left, right, top, bottom): 

2614 ''' 

2615 Give margins fixed size constraints. 

2616 ''' 

2617 

2618 self.left.set_horizontal(left, 0) 

2619 self.right.set_horizontal(right, 0) 

2620 self.top.set_vertical(top, 0) 

2621 self.bottom.set_vertical(bottom, 0) 

2622 

2623 def set_min_margins(self, left, right, top, bottom, grow=0.0001): 

2624 ''' 

2625 Give margins a minimal size and the possibility to grow. 

2626 

2627 The desire to grow is set to a very small number. 

2628 ''' 

2629 self.left.set_horizontal(left, grow) 

2630 self.right.set_horizontal(right, grow) 

2631 self.top.set_vertical(top, grow) 

2632 self.bottom.set_vertical(bottom, grow) 

2633 

2634 def get_min_size(self): 

2635 shs, svs = Widget.get_min_size(self) 

2636 

2637 sl, sr, st, sb, sc = [x.get_min_size() for x in ( 

2638 self.left, self.right, self.top, self.bottom, self.center)] 

2639 gl, gr, gt, gb, gc = [x.get_grow() for x in ( 

2640 self.left, self.right, self.top, self.bottom, self.center)] 

2641 

2642 shsum = sl[0]+sr[0]+sc[0] 

2643 svsum = st[1]+sb[1]+sc[1] 

2644 

2645 # prevent widgets from collapsing 

2646 for s, g in ((sl, gl), (sr, gr), (sc, gc)): 

2647 if s[0] == 0.0 and g[0] != 0.0: 

2648 shsum += 0.1*cm 

2649 

2650 for s, g in ((st, gt), (sb, gb), (sc, gc)): 

2651 if s[1] == 0.0 and g[1] != 0.0: 

2652 svsum += 0.1*cm 

2653 

2654 sh = max(shs, shsum) 

2655 sv = max(svs, svsum) 

2656 

2657 return sh, sv 

2658 

2659 def get_grow(self): 

2660 ghs, gvs = Widget.get_grow(self) 

2661 gh = (self.left.get_grow()[0] + 

2662 self.right.get_grow()[0] + 

2663 self.center.get_grow()[0]) * ghs 

2664 gv = (self.top.get_grow()[1] + 

2665 self.bottom.get_grow()[1] + 

2666 self.center.get_grow()[1]) * gvs 

2667 return gh, gv 

2668 

2669 def set_size(self, size, offset): 

2670 (sh, sv), (oh, ov) = self.legalize(size, offset) 

2671 

2672 sl, sr, st, sb, sc = [x.get_min_size() for x in ( 

2673 self.left, self.right, self.top, self.bottom, self.center)] 

2674 gl, gr, gt, gb, gc = [x.get_grow() for x in ( 

2675 self.left, self.right, self.top, self.bottom, self.center)] 

2676 

2677 ah = sh - (sl[0]+sr[0]+sc[0]) 

2678 av = sv - (st[1]+sb[1]+sc[1]) 

2679 

2680 if ah < 0.0: 

2681 raise GmtPyError('Container not wide enough for contents ' 

2682 '(FrameLayout, available: %g cm, needed: %g cm)' 

2683 % (sh/cm, (sl[0]+sr[0]+sc[0])/cm)) 

2684 if av < 0.0: 

2685 raise GmtPyError('Container not high enough for contents ' 

2686 '(FrameLayout, available: %g cm, needed: %g cm)' 

2687 % (sv/cm, (st[1]+sb[1]+sc[1])/cm)) 

2688 

2689 slh, srh, sch = distribute((sl[0], sr[0], sc[0]), 

2690 (gl[0], gr[0], gc[0]), ah) 

2691 stv, sbv, scv = distribute((st[1], sb[1], sc[1]), 

2692 (gt[1], gb[1], gc[1]), av) 

2693 

2694 if self.center.aspect is not None: 

2695 ahm = sh - (sl[0]+sr[0] + scv/self.center.aspect) 

2696 avm = sv - (st[1]+sb[1] + sch*self.center.aspect) 

2697 if 0.0 < ahm < ah: 

2698 slh, srh, sch = distribute( 

2699 (sl[0], sr[0], scv/self.center.aspect), 

2700 (gl[0], gr[0], 0.0), ahm) 

2701 

2702 elif 0.0 < avm < av: 

2703 stv, sbv, scv = distribute((st[1], sb[1], 

2704 sch*self.center.aspect), 

2705 (gt[1], gb[1], 0.0), avm) 

2706 

2707 ah = sh - (slh+srh+sch) 

2708 av = sv - (stv+sbv+scv) 

2709 

2710 oh += ah/2. 

2711 ov += av/2. 

2712 sh -= ah 

2713 sv -= av 

2714 

2715 self.left.set_size((slh, scv), (oh, ov+sbv)) 

2716 self.right.set_size((srh, scv), (oh+slh+sch, ov+sbv)) 

2717 self.top.set_size((sh, stv), (oh, ov+sbv+scv)) 

2718 self.bottom.set_size((sh, sbv), (oh, ov)) 

2719 self.center.set_size((sch, scv), (oh+slh, ov+sbv)) 

2720 Widget.set_size(self, (sh, sv), (oh, ov)) 

2721 

2722 def set_widget(self, which='center', widget=None): 

2723 

2724 ''' 

2725 Set one of the sub-widgets. 

2726 

2727 ``which`` should be one of ``'left'``, ``'right'``, ``'top'``, 

2728 ``'bottom'`` or ``'center'``. 

2729 ''' 

2730 

2731 if widget is None: 

2732 widget = Widget() 

2733 

2734 if which in ('left', 'right', 'top', 'bottom', 'center'): 

2735 self.__dict__[which] = widget 

2736 else: 

2737 raise GmtPyError('No such sub-widget: %s' % which) 

2738 

2739 widget.set_parent(self) 

2740 

2741 def get_widget(self, which='center'): 

2742 

2743 ''' 

2744 Get one of the sub-widgets. 

2745 

2746 ``which`` should be one of ``'left'``, ``'right'``, ``'top'``, 

2747 ``'bottom'`` or ``'center'``. 

2748 ''' 

2749 

2750 if which in ('left', 'right', 'top', 'bottom', 'center'): 

2751 return self.__dict__[which] 

2752 else: 

2753 raise GmtPyError('No such sub-widget: %s' % which) 

2754 

2755 def get_children(self): 

2756 return [self.left, self.right, self.top, self.bottom, self.center] 

2757 

2758 

2759class GridLayout(Widget): 

2760 

2761 ''' 

2762 A layout manager which arranges its sub-widgets in a grid. 

2763 

2764 The grid spacing is flexible and based on the sizing policies of the 

2765 contained sub-widgets. If an equidistant grid is needed, the sizing 

2766 policies of the sub-widgets have to be set equally. 

2767 

2768 The height of each row and the width of each column is derived from the 

2769 sizing policy of the largest sub-widget in the row or column in question. 

2770 The algorithm is not very sophisticated, so conflicting sizing policies 

2771 might not be resolved optimally. 

2772 ''' 

2773 

2774 def __init__(self, nx=2, ny=2, horizontal=None, vertical=None): 

2775 

2776 ''' 

2777 Create new grid layout with ``nx`` columns and ``ny`` rows. 

2778 ''' 

2779 

2780 Widget.__init__(self, horizontal, vertical) 

2781 self.grid = [] 

2782 for iy in range(ny): 

2783 row = [] 

2784 for ix in range(nx): 

2785 w = Widget(parent=self) 

2786 row.append(w) 

2787 

2788 self.grid.append(row) 

2789 

2790 def sub_min_sizes_as_array(self): 

2791 esh = num.array( 

2792 [[w.get_min_size()[0] for w in row] for row in self.grid], 

2793 dtype=float) 

2794 esv = num.array( 

2795 [[w.get_min_size()[1] for w in row] for row in self.grid], 

2796 dtype=float) 

2797 return esh, esv 

2798 

2799 def sub_grows_as_array(self): 

2800 egh = num.array( 

2801 [[w.get_grow()[0] for w in row] for row in self.grid], 

2802 dtype=float) 

2803 egv = num.array( 

2804 [[w.get_grow()[1] for w in row] for row in self.grid], 

2805 dtype=float) 

2806 return egh, egv 

2807 

2808 def get_min_size(self): 

2809 sh, sv = Widget.get_min_size(self) 

2810 esh, esv = self.sub_min_sizes_as_array() 

2811 if esh.size != 0: 

2812 sh = max(sh, num.sum(esh.max(0))) 

2813 if esv.size != 0: 

2814 sv = max(sv, num.sum(esv.max(1))) 

2815 return sh, sv 

2816 

2817 def get_grow(self): 

2818 ghs, gvs = Widget.get_grow(self) 

2819 egh, egv = self.sub_grows_as_array() 

2820 if egh.size != 0: 

2821 gh = num.sum(egh.max(0))*ghs 

2822 else: 

2823 gh = 1.0 

2824 if egv.size != 0: 

2825 gv = num.sum(egv.max(1))*gvs 

2826 else: 

2827 gv = 1.0 

2828 return gh, gv 

2829 

2830 def set_size(self, size, offset): 

2831 (sh, sv), (oh, ov) = self.legalize(size, offset) 

2832 esh, esv = self.sub_min_sizes_as_array() 

2833 egh, egv = self.sub_grows_as_array() 

2834 

2835 # available additional space 

2836 empty = esh.size == 0 

2837 

2838 if not empty: 

2839 ah = sh - num.sum(esh.max(0)) 

2840 av = sv - num.sum(esv.max(1)) 

2841 else: 

2842 av = sv 

2843 ah = sh 

2844 

2845 if ah < 0.0: 

2846 raise GmtPyError('Container not wide enough for contents ' 

2847 '(GridLayout, available: %g cm, needed: %g cm)' 

2848 % (sh/cm, (num.sum(esh.max(0)))/cm)) 

2849 if av < 0.0: 

2850 raise GmtPyError('Container not high enough for contents ' 

2851 '(GridLayout, available: %g cm, needed: %g cm)' 

2852 % (sv/cm, (num.sum(esv.max(1)))/cm)) 

2853 

2854 nx, ny = esh.shape 

2855 

2856 if not empty: 

2857 # distribute additional space on rows and columns 

2858 # according to grow weights and minimal sizes 

2859 gsh = egh.sum(1)[:, num.newaxis].repeat(ny, axis=1) 

2860 nesh = esh.copy() 

2861 nesh += num.where(gsh > 0.0, ah*egh/gsh, 0.0) 

2862 

2863 nsh = num.maximum(nesh.max(0), esh.max(0)) 

2864 

2865 gsv = egv.sum(0)[num.newaxis, :].repeat(nx, axis=0) 

2866 nesv = esv.copy() 

2867 nesv += num.where(gsv > 0.0, av*egv/gsv, 0.0) 

2868 nsv = num.maximum(nesv.max(1), esv.max(1)) 

2869 

2870 ah = sh - sum(nsh) 

2871 av = sv - sum(nsv) 

2872 

2873 oh += ah/2. 

2874 ov += av/2. 

2875 sh -= ah 

2876 sv -= av 

2877 

2878 # resize child widgets 

2879 neov = ov + sum(nsv) 

2880 for row, nesv in zip(self.grid, nsv): 

2881 neov -= nesv 

2882 neoh = oh 

2883 for w, nesh in zip(row, nsh): 

2884 w.set_size((nesh, nesv), (neoh, neov)) 

2885 neoh += nesh 

2886 

2887 Widget.set_size(self, (sh, sv), (oh, ov)) 

2888 

2889 def set_widget(self, ix, iy, widget=None): 

2890 

2891 ''' 

2892 Set one of the sub-widgets. 

2893 

2894 Sets the sub-widget in column ``ix`` and row ``iy``. The indices are 

2895 counted from zero. 

2896 ''' 

2897 

2898 if widget is None: 

2899 widget = Widget() 

2900 

2901 self.grid[iy][ix] = widget 

2902 widget.set_parent(self) 

2903 

2904 def get_widget(self, ix, iy): 

2905 

2906 ''' 

2907 Get one of the sub-widgets. 

2908 

2909 Gets the sub-widget from column ``ix`` and row ``iy``. The indices are 

2910 counted from zero. 

2911 ''' 

2912 

2913 return self.grid[iy][ix] 

2914 

2915 def get_children(self): 

2916 children = [] 

2917 for row in self.grid: 

2918 children.extend(row) 

2919 

2920 return children 

2921 

2922 

2923def is_gmt5(version='newest'): 

2924 return get_gmt_installation(version)['version'][0] in ['5', '6'] 

2925 

2926 

2927def is_gmt6(version='newest'): 

2928 return get_gmt_installation(version)['version'][0] in ['6'] 

2929 

2930 

2931def aspect_for_projection(gmtversion, *args, **kwargs): 

2932 

2933 gmt = GMT(version=gmtversion, eps_mode=True) 

2934 

2935 if gmt.is_gmt5(): 

2936 gmt.psbasemap('-B+gblack', finish=True, *args, **kwargs) 

2937 fn = gmt.tempfilename('test.eps') 

2938 gmt.save(fn, crop_eps_mode=True) 

2939 with open(fn, 'rb') as f: 

2940 s = f.read() 

2941 

2942 l, b, r, t = get_bbox(s) # noqa 

2943 else: 

2944 gmt.psbasemap('-G0', finish=True, *args, **kwargs) 

2945 l, b, r, t = gmt.bbox() # noqa 

2946 

2947 return (t-b)/(r-l) # noqa 

2948 

2949 

2950def text_box( 

2951 text, font=0, font_size=12., angle=0, gmtversion='newest', **kwargs): 

2952 

2953 gmt = GMT(version=gmtversion) 

2954 if gmt.is_gmt5(): 

2955 row = [0, 0, text] 

2956 farg = ['-F+f%gp,%s,%s+j%s' % (font_size, font, 'black', 'BL')] 

2957 else: 

2958 row = [0, 0, font_size, 0, font, 'BL', text] 

2959 farg = [] 

2960 

2961 gmt.pstext( 

2962 in_rows=[row], 

2963 finish=True, 

2964 R=(0, 1, 0, 1), 

2965 J='x10p', 

2966 N=True, 

2967 *farg, 

2968 **kwargs) 

2969 

2970 fn = gmt.tempfilename() + '.ps' 

2971 gmt.save(fn) 

2972 

2973 (_, stderr) = subprocess.Popen( 

2974 ['gs', '-q', '-dNOPAUSE', '-dBATCH', '-r720', '-sDEVICE=bbox', fn], 

2975 stderr=subprocess.PIPE).communicate() 

2976 

2977 dx, dy = None, None 

2978 for line in stderr.splitlines(): 

2979 if line.startswith(b'%%HiResBoundingBox:'): 

2980 l, b, r, t = [float(x) for x in line.split()[-4:]] # noqa 

2981 dx, dy = r-l, t-b # noqa 

2982 break 

2983 

2984 return dx, dy 

2985 

2986 

2987class TableLiner(object): 

2988 ''' 

2989 Utility class to turn tables into lines. 

2990 ''' 

2991 

2992 def __init__(self, in_columns=None, in_rows=None, encoding='utf-8'): 

2993 self.in_columns = in_columns 

2994 self.in_rows = in_rows 

2995 self.encoding = encoding 

2996 

2997 def __iter__(self): 

2998 if self.in_columns is not None: 

2999 for row in zip(*self.in_columns): 

3000 yield (' '.join([str(x) for x in row])+'\n').encode( 

3001 self.encoding) 

3002 

3003 if self.in_rows is not None: 

3004 for row in self.in_rows: 

3005 yield (' '.join([str(x) for x in row])+'\n').encode( 

3006 self.encoding) 

3007 

3008 

3009class LineStreamChopper(object): 

3010 ''' 

3011 File-like object to buffer data. 

3012 ''' 

3013 

3014 def __init__(self, liner): 

3015 self.chopsize = None 

3016 self.liner = liner 

3017 self.chop_iterator = None 

3018 self.closed = False 

3019 

3020 def _chopiter(self): 

3021 buf = BytesIO() 

3022 for line in self.liner: 

3023 buf.write(line) 

3024 buflen = buf.tell() 

3025 if self.chopsize is not None and buflen >= self.chopsize: 

3026 buf.seek(0) 

3027 while buf.tell() <= buflen-self.chopsize: 

3028 yield buf.read(self.chopsize) 

3029 

3030 newbuf = BytesIO() 

3031 newbuf.write(buf.read()) 

3032 buf.close() 

3033 buf = newbuf 

3034 

3035 yield buf.getvalue() 

3036 buf.close() 

3037 

3038 def read(self, size=None): 

3039 if self.closed: 

3040 raise ValueError('Cannot read from closed LineStreamChopper.') 

3041 if self.chop_iterator is None: 

3042 self.chopsize = size 

3043 self.chop_iterator = self._chopiter() 

3044 

3045 self.chopsize = size 

3046 try: 

3047 return next(self.chop_iterator) 

3048 except StopIteration: 

3049 return '' 

3050 

3051 def close(self): 

3052 self.chopsize = None 

3053 self.chop_iterator = None 

3054 self.closed = True 

3055 

3056 def flush(self): 

3057 pass 

3058 

3059 

3060font_tab = { 

3061 0: 'Helvetica', 

3062 1: 'Helvetica-Bold', 

3063} 

3064 

3065font_tab_rev = dict((v, k) for (k, v) in font_tab.items()) 

3066 

3067 

3068class GMT(object): 

3069 ''' 

3070 A thin wrapper to GMT command execution. 

3071 

3072 A dict ``config`` may be given to override some of the default GMT 

3073 parameters. The ``version`` argument may be used to select a specific GMT 

3074 version, which should be used with this GMT instance. The selected 

3075 version of GMT has to be installed on the system, must be supported by 

3076 gmtpy and gmtpy must know where to find it. 

3077 

3078 Each instance of this class is used for the task of producing one PS or PDF 

3079 output file. 

3080 

3081 Output of a series of GMT commands is accumulated in memory and can then be 

3082 saved as PS or PDF file using the :py:meth:`save` method. 

3083 

3084 GMT commands are accessed as method calls to instances of this class. See 

3085 the :py:meth:`__getattr__` method for details on how the method's 

3086 arguments are translated into options and arguments for the GMT command. 

3087 

3088 Associated with each instance of this class, a temporary directory is 

3089 created, where temporary files may be created, and which is automatically 

3090 deleted, when the object is destroyed. The :py:meth:`tempfilename` method 

3091 may be used to get a random filename in the instance's temporary directory. 

3092 

3093 Any .gmtdefaults files are ignored. The GMT class uses a fixed 

3094 set of defaults, which may be altered via an argument to the constructor. 

3095 If possible, GMT is run in 'isolation mode', which was introduced with GMT 

3096 version 4.2.2, by setting `GMT_TMPDIR` to the instance's temporary 

3097 directory. With earlier versions of GMT, problems may arise with parallel 

3098 execution of more than one GMT instance. 

3099 

3100 Each instance of the GMT class may pick a specific version of GMT which 

3101 shall be used, so that, if multiple versions of GMT are installed on the 

3102 system, different versions of GMT can be used simultaneously such that 

3103 backward compatibility of the scripts can be maintained. 

3104 

3105 ''' 

3106 

3107 def __init__( 

3108 self, 

3109 config=None, 

3110 kontinue=None, 

3111 version='newest', 

3112 config_papersize=None, 

3113 eps_mode=False): 

3114 

3115 self.installation = get_gmt_installation(version) 

3116 self.gmt_config = dict(self.installation['defaults']) 

3117 self.eps_mode = eps_mode 

3118 self._shutil = shutil 

3119 

3120 if config: 

3121 self.gmt_config.update(config) 

3122 

3123 if config_papersize: 

3124 if not isinstance(config_papersize, str): 

3125 config_papersize = 'Custom_%ix%i' % ( 

3126 int(config_papersize[0]), int(config_papersize[1])) 

3127 

3128 if self.is_gmt5(): 

3129 self.gmt_config['PS_MEDIA'] = config_papersize 

3130 else: 

3131 self.gmt_config['PAPER_MEDIA'] = config_papersize 

3132 

3133 self.tempdir = tempfile.mkdtemp('', 'gmtpy-') 

3134 self.gmt_config_filename = pjoin(self.tempdir, 'gmt.conf') 

3135 self.gen_gmt_config_file(self.gmt_config_filename, self.gmt_config) 

3136 

3137 if kontinue is not None: 

3138 self.load_unfinished(kontinue) 

3139 self.needstart = False 

3140 else: 

3141 self.output = BytesIO() 

3142 self.needstart = True 

3143 

3144 self.finished = False 

3145 

3146 self.environ = os.environ.copy() 

3147 self.environ['GMTHOME'] = self.installation.get('home', '') 

3148 # GMT isolation mode: works only properly with GMT version >= 4.2.2 

3149 self.environ['GMT_TMPDIR'] = self.tempdir 

3150 

3151 self.layout = None 

3152 self.command_log = [] 

3153 self.keep_temp_dir = False 

3154 

3155 def is_gmt5(self): 

3156 return self.get_version()[0] in ['5', '6'] 

3157 

3158 def is_gmt6(self): 

3159 return self.get_version()[0] in ['6'] 

3160 

3161 def get_version(self): 

3162 return self.installation['version'] 

3163 

3164 def get_config(self, key): 

3165 return self.gmt_config[key] 

3166 

3167 def to_points(self, string): 

3168 if not string: 

3169 return 0 

3170 

3171 unit = string[-1] 

3172 if unit in _units: 

3173 return float(string[:-1])/_units[unit] 

3174 else: 

3175 default_unit = measure_unit(self.gmt_config).lower()[0] 

3176 return float(string)/_units[default_unit] 

3177 

3178 def label_font_size(self): 

3179 if self.is_gmt5(): 

3180 return self.to_points(self.gmt_config['FONT_LABEL'].split(',')[0]) 

3181 else: 

3182 return self.to_points(self.gmt_config['LABEL_FONT_SIZE']) 

3183 

3184 def label_font(self): 

3185 if self.is_gmt5(): 

3186 return font_tab_rev(self.gmt_config['FONT_LABEL'].split(',')[1]) 

3187 else: 

3188 return self.gmt_config['LABEL_FONT'] 

3189 

3190 def gen_gmt_config_file(self, config_filename, config): 

3191 f = open(config_filename, 'wb') 

3192 f.write( 

3193 ('#\n# GMT %s Defaults file\n' 

3194 % self.installation['version']).encode('ascii')) 

3195 

3196 for k, v in config.items(): 

3197 f.write(('%s = %s\n' % (k, v)).encode('ascii')) 

3198 f.close() 

3199 

3200 def __del__(self): 

3201 if not self.keep_temp_dir: 

3202 self._shutil.rmtree(self.tempdir) 

3203 

3204 def _gmtcommand(self, command, *addargs, **kwargs): 

3205 

3206 ''' 

3207 Execute arbitrary GMT command. 

3208 

3209 See docstring in __getattr__ for details. 

3210 ''' 

3211 

3212 in_stream = kwargs.pop('in_stream', None) 

3213 in_filename = kwargs.pop('in_filename', None) 

3214 in_string = kwargs.pop('in_string', None) 

3215 in_columns = kwargs.pop('in_columns', None) 

3216 in_rows = kwargs.pop('in_rows', None) 

3217 out_stream = kwargs.pop('out_stream', None) 

3218 out_filename = kwargs.pop('out_filename', None) 

3219 out_discard = kwargs.pop('out_discard', None) 

3220 finish = kwargs.pop('finish', False) 

3221 suppressdefaults = kwargs.pop('suppress_defaults', False) 

3222 config_override = kwargs.pop('config', None) 

3223 

3224 assert not self.finished 

3225 

3226 # check for mutual exclusiveness on input and output possibilities 

3227 assert (1 >= len( 

3228 [x for x in [ 

3229 in_stream, in_filename, in_string, in_columns, in_rows] 

3230 if x is not None])) 

3231 assert (1 >= len([x for x in [out_stream, out_filename, out_discard] 

3232 if x is not None])) 

3233 

3234 options = [] 

3235 

3236 gmt_config = self.gmt_config 

3237 if not self.is_gmt5(): 

3238 gmt_config_filename = self.gmt_config_filename 

3239 if config_override: 

3240 gmt_config = self.gmt_config.copy() 

3241 gmt_config.update(config_override) 

3242 gmt_config_override_filename = pjoin( 

3243 self.tempdir, 'gmtdefaults_override') 

3244 self.gen_gmt_config_file( 

3245 gmt_config_override_filename, gmt_config) 

3246 gmt_config_filename = gmt_config_override_filename 

3247 

3248 else: # gmt5 needs override variables as --VAR=value 

3249 if config_override: 

3250 for k, v in config_override.items(): 

3251 options.append('--%s=%s' % (k, v)) 

3252 

3253 if out_discard: 

3254 out_filename = '/dev/null' 

3255 

3256 out_mustclose = False 

3257 if out_filename is not None: 

3258 out_mustclose = True 

3259 out_stream = open(out_filename, 'wb') 

3260 

3261 if in_filename is not None: 

3262 in_stream = open(in_filename, 'rb') 

3263 

3264 if in_string is not None: 

3265 in_stream = BytesIO(in_string) 

3266 

3267 encoding_gmt = gmt_config.get( 

3268 'PS_CHAR_ENCODING', 

3269 gmt_config.get('CHAR_ENCODING', 'ISOLatin1+')) 

3270 

3271 encoding = encoding_gmt_to_python[encoding_gmt.lower()] 

3272 

3273 if in_columns is not None or in_rows is not None: 

3274 in_stream = LineStreamChopper(TableLiner(in_columns=in_columns, 

3275 in_rows=in_rows, 

3276 encoding=encoding)) 

3277 

3278 # convert option arguments to strings 

3279 for k, v in kwargs.items(): 

3280 if len(k) > 1: 

3281 raise GmtPyError('Found illegal keyword argument "%s" ' 

3282 'while preparing options for command "%s"' 

3283 % (k, command)) 

3284 

3285 if type(v) is bool: 

3286 if v: 

3287 options.append('-%s' % k) 

3288 elif type(v) is tuple or type(v) is list: 

3289 options.append('-%s' % k + '/'.join([str(x) for x in v])) 

3290 else: 

3291 options.append('-%s%s' % (k, str(v))) 

3292 

3293 # if not redirecting to an external sink, handle -K -O 

3294 if out_stream is None: 

3295 if not finish: 

3296 options.append('-K') 

3297 else: 

3298 self.finished = True 

3299 

3300 if not self.needstart: 

3301 options.append('-O') 

3302 else: 

3303 self.needstart = False 

3304 

3305 out_stream = self.output 

3306 

3307 # run the command 

3308 if self.is_gmt5(): 

3309 args = [pjoin(self.installation['bin'], 'gmt'), command] 

3310 else: 

3311 args = [pjoin(self.installation['bin'], command)] 

3312 

3313 if not os.path.isfile(args[0]): 

3314 raise OSError('No such file: %s' % args[0]) 

3315 args.extend(options) 

3316 args.extend(addargs) 

3317 if not self.is_gmt5() and not suppressdefaults: 

3318 # does not seem to work with GMT 5 (and should not be necessary 

3319 args.append('+'+gmt_config_filename) 

3320 

3321 bs = 2048 

3322 p = subprocess.Popen(args, stdin=subprocess.PIPE, 

3323 stdout=subprocess.PIPE, bufsize=bs, 

3324 env=self.environ) 

3325 while True: 

3326 cr, cw, cx = select([p.stdout], [p.stdin], []) 

3327 if cr: 

3328 out_stream.write(p.stdout.read(bs)) 

3329 if cw: 

3330 if in_stream is not None: 

3331 data = in_stream.read(bs) 

3332 if len(data) == 0: 

3333 break 

3334 p.stdin.write(data) 

3335 else: 

3336 break 

3337 if not cr and not cw: 

3338 break 

3339 

3340 p.stdin.close() 

3341 

3342 while True: 

3343 data = p.stdout.read(bs) 

3344 if len(data) == 0: 

3345 break 

3346 out_stream.write(data) 

3347 

3348 p.stdout.close() 

3349 

3350 retcode = p.wait() 

3351 

3352 if in_stream is not None: 

3353 in_stream.close() 

3354 

3355 if out_mustclose: 

3356 out_stream.close() 

3357 

3358 if retcode != 0: 

3359 self.keep_temp_dir = True 

3360 raise GMTError('Command %s returned an error. ' 

3361 'While executing command:\n%s' 

3362 % (command, escape_shell_args(args))) 

3363 

3364 self.command_log.append(args) 

3365 

3366 def __getattr__(self, command): 

3367 

3368 ''' 

3369 Maps to call self._gmtcommand(command, \\*addargs, \\*\\*kwargs). 

3370 

3371 Execute arbitrary GMT command. 

3372 

3373 Run a GMT command and by default append its postscript output to the 

3374 output file maintained by the GMT instance on which this method is 

3375 called. 

3376 

3377 Except for a few keyword arguments listed below, any ``kwargs`` and 

3378 ``addargs`` are converted into command line options and arguments and 

3379 passed to the GMT command. Numbers in keyword arguments are converted 

3380 into strings. E.g. ``S=10`` is translated into ``'-S10'``. Tuples of 

3381 numbers or strings are converted into strings where the elements of the 

3382 tuples are separated by slashes '/'. E.g. ``R=(10, 10, 20, 20)`` is 

3383 translated into ``'-R10/10/20/20'``. Options with a boolean argument 

3384 are only appended to the GMT command, if their values are True. 

3385 

3386 If no output redirection is in effect, the -K and -O options are 

3387 handled by gmtpy and thus should not be specified. Use 

3388 ``out_discard=True`` if you don't want -K or -O beeing added, but are 

3389 not interested in the output. 

3390 

3391 The standard input of the GMT process is fed by data selected with one 

3392 of the following ``in_*`` keyword arguments: 

3393 

3394 =============== ======================================================= 

3395 ``in_stream`` Data is read from an open file like object. 

3396 ``in_filename`` Data is read from the given file. 

3397 ``in_string`` String content is dumped to the process. 

3398 ``in_columns`` A 2D nested iterable whose elements can be accessed as 

3399 ``in_columns[icolumn][irow]`` is converted into an 

3400 ascii 

3401 table, which is fed to the process. 

3402 ``in_rows`` A 2D nested iterable whos elements can be accessed as 

3403 ``in_rows[irow][icolumn]`` is converted into an ascii 

3404 table, which is fed to the process. 

3405 =============== ======================================================= 

3406 

3407 The standard output of the GMT process may be redirected by one of the 

3408 following options: 

3409 

3410 ================= ===================================================== 

3411 ``out_stream`` Output is fed to an open file like object. 

3412 ``out_filename`` Output is dumped to the given file. 

3413 ``out_discard`` If True, output is dumped to :file:`/dev/null`. 

3414 ================= ===================================================== 

3415 

3416 Additional keyword arguments: 

3417 

3418 ===================== ================================================= 

3419 ``config`` Dict with GMT defaults which override the 

3420 currently active set of defaults exclusively 

3421 during this call. 

3422 ``finish`` If True, the postscript file, which is maintained 

3423 by the GMT instance is finished, and no further 

3424 plotting is allowed. 

3425 ``suppress_defaults`` Suppress appending of the ``'+gmtdefaults'`` 

3426 option to the command. 

3427 ===================== ================================================= 

3428 

3429 ''' 

3430 

3431 def f(*args, **kwargs): 

3432 return self._gmtcommand(command, *args, **kwargs) 

3433 return f 

3434 

3435 def tempfilename(self, name=None): 

3436 ''' 

3437 Get filename for temporary file in the private temp directory. 

3438 

3439 If no ``name`` argument is given, a random name is picked. If 

3440 ``name`` is given, returns a path ending in that ``name``. 

3441 ''' 

3442 

3443 if not name: 

3444 name = ''.join( 

3445 [random.choice('abcdefghijklmnopqrstuvwxyz') 

3446 for i in range(10)]) 

3447 

3448 fn = pjoin(self.tempdir, name) 

3449 return fn 

3450 

3451 def tempfile(self, name=None): 

3452 ''' 

3453 Create and open a file in the private temp directory. 

3454 ''' 

3455 

3456 fn = self.tempfilename(name) 

3457 f = open(fn, 'wb') 

3458 return f, fn 

3459 

3460 def save_unfinished(self, filename): 

3461 out = open(filename, 'wb') 

3462 out.write(self.output.getvalue()) 

3463 out.close() 

3464 

3465 def load_unfinished(self, filename): 

3466 self.output = BytesIO() 

3467 self.finished = False 

3468 inp = open(filename, 'rb') 

3469 self.output.write(inp.read()) 

3470 inp.close() 

3471 

3472 def dump(self, ident): 

3473 filename = self.tempfilename('breakpoint-%s' % ident) 

3474 self.save_unfinished(filename) 

3475 

3476 def load(self, ident): 

3477 filename = self.tempfilename('breakpoint-%s' % ident) 

3478 self.load_unfinished(filename) 

3479 

3480 def save(self, filename=None, bbox=None, resolution=150, oversample=2., 

3481 width=None, height=None, size=None, crop_eps_mode=False, 

3482 psconvert=False): 

3483 

3484 ''' 

3485 Finish and save figure as PDF, PS or PPM file. 

3486 

3487 If filename ends with ``'.pdf'`` a PDF file is created by piping the 

3488 GMT output through :program:`gmtpy-epstopdf`. 

3489 

3490 If filename ends with ``'.png'`` a PNG file is created by running 

3491 :program:`gmtpy-epstopdf`, :program:`pdftocairo` and 

3492 :program:`convert`. ``resolution`` specifies the resolution in DPI for 

3493 raster file formats. Rasterization is done at a higher resolution if 

3494 ``oversample`` is set to a value higher than one. The output image size 

3495 can also be controlled by setting ``width``, ``height`` or ``size`` 

3496 instead of ``resolution``. When ``size`` is given, the image is scaled 

3497 so that ``max(width, height) == size``. 

3498 

3499 The bounding box is set according to the values given in ``bbox``. 

3500 ''' 

3501 

3502 if not self.finished: 

3503 self.psxy(R=True, J=True, finish=True) 

3504 

3505 if filename: 

3506 tempfn = pjoin(self.tempdir, 'incomplete') 

3507 out = open(tempfn, 'wb') 

3508 else: 

3509 out = sys.stdout 

3510 

3511 if bbox and not self.is_gmt5(): 

3512 out.write(replace_bbox(bbox, self.output.getvalue())) 

3513 else: 

3514 out.write(self.output.getvalue()) 

3515 

3516 if filename: 

3517 out.close() 

3518 

3519 if filename.endswith('.ps') or ( 

3520 not self.is_gmt5() and filename.endswith('.eps')): 

3521 

3522 shutil.move(tempfn, filename) 

3523 return 

3524 

3525 if self.is_gmt5(): 

3526 if crop_eps_mode: 

3527 addarg = ['-A'] 

3528 else: 

3529 addarg = [] 

3530 

3531 subprocess.call( 

3532 [pjoin(self.installation['bin'], 'gmt'), 'psconvert', 

3533 '-Te', '-F%s' % tempfn, tempfn, ] + addarg) 

3534 

3535 if bbox: 

3536 with open(tempfn + '.eps', 'rb') as fin: 

3537 with open(tempfn + '-fixbb.eps', 'wb') as fout: 

3538 replace_bbox(bbox, fin, fout) 

3539 

3540 shutil.move(tempfn + '-fixbb.eps', tempfn + '.eps') 

3541 

3542 else: 

3543 shutil.move(tempfn, tempfn + '.eps') 

3544 

3545 if filename.endswith('.eps'): 

3546 shutil.move(tempfn + '.eps', filename) 

3547 return 

3548 

3549 elif filename.endswith('.pdf'): 

3550 if psconvert: 

3551 gmt_bin = pjoin(self.installation['bin'], 'gmt') 

3552 subprocess.call([gmt_bin, 'psconvert', tempfn + '.eps', '-Tf', 

3553 '-F' + filename]) 

3554 else: 

3555 subprocess.call(['gmtpy-epstopdf', '--res=%i' % resolution, 

3556 '--outfile=' + filename, tempfn + '.eps']) 

3557 else: 

3558 subprocess.call([ 

3559 'gmtpy-epstopdf', 

3560 '--res=%i' % (resolution * oversample), 

3561 '--outfile=' + tempfn + '.pdf', tempfn + '.eps']) 

3562 

3563 convert_graph( 

3564 tempfn + '.pdf', filename, 

3565 resolution=resolution, oversample=oversample, 

3566 size=size, width=width, height=height) 

3567 

3568 def bbox(self): 

3569 return get_bbox(self.output.getvalue()) 

3570 

3571 def get_command_log(self): 

3572 ''' 

3573 Get the command log. 

3574 ''' 

3575 

3576 return self.command_log 

3577 

3578 def __str__(self): 

3579 s = '' 

3580 for com in self.command_log: 

3581 s += com[0] + '\n ' + '\n '.join(com[1:]) + '\n\n' 

3582 return s 

3583 

3584 def page_size_points(self): 

3585 ''' 

3586 Try to get paper size of output postscript file in points. 

3587 ''' 

3588 

3589 pm = paper_media(self.gmt_config).lower() 

3590 if pm.endswith('+') or pm.endswith('-'): 

3591 pm = pm[:-1] 

3592 

3593 orient = page_orientation(self.gmt_config).lower() 

3594 

3595 if pm in all_paper_sizes(): 

3596 

3597 if orient == 'portrait': 

3598 return get_paper_size(pm) 

3599 else: 

3600 return get_paper_size(pm)[1], get_paper_size(pm)[0] 

3601 

3602 m = re.match(r'custom_([0-9.]+)([cimp]?)x([0-9.]+)([cimp]?)', pm) 

3603 if m: 

3604 w, uw, h, uh = m.groups() 

3605 w, h = float(w), float(h) 

3606 if uw: 

3607 w *= _units[uw] 

3608 if uh: 

3609 h *= _units[uh] 

3610 if orient == 'portrait': 

3611 return w, h 

3612 else: 

3613 return h, w 

3614 

3615 return None, None 

3616 

3617 def default_layout(self, with_palette=False): 

3618 ''' 

3619 Get a default layout for the output page. 

3620 

3621 One of three different layouts is choosen, depending on the 

3622 `PAPER_MEDIA` setting in the GMT configuration dict. 

3623 

3624 If `PAPER_MEDIA` ends with a ``'+'`` (EPS output is selected), a 

3625 :py:class:`FrameLayout` is centered on the page, whose size is 

3626 controlled by its center widget's size plus the margins of the 

3627 :py:class:`FrameLayout`. 

3628 

3629 If `PAPER_MEDIA` indicates, that a custom page size is wanted by 

3630 starting with ``'Custom_'``, a :py:class:`FrameLayout` is used to fill 

3631 the complete page. The center widget's size is then controlled by the 

3632 page's size minus the margins of the :py:class:`FrameLayout`. 

3633 

3634 In any other case, two FrameLayouts are nested, such that the outer 

3635 layout attaches a 1 cm (printer) margin around the complete page, and 

3636 the inner FrameLayout's center widget takes up as much space as 

3637 possible under the constraint, that an aspect ratio of 1/golden_ratio 

3638 is preserved. 

3639 

3640 In any case, a reference to the innermost :py:class:`FrameLayout` 

3641 instance is returned. The top-level layout can be accessed by calling 

3642 :py:meth:`Widget.get_parent` on the returned layout. 

3643 ''' 

3644 

3645 if self.layout is None: 

3646 w, h = self.page_size_points() 

3647 

3648 if w is None or h is None: 

3649 raise GmtPyError("Can't determine page size for layout") 

3650 

3651 pm = paper_media(self.gmt_config).lower() 

3652 

3653 if with_palette: 

3654 palette_layout = GridLayout(3, 1) 

3655 spacer = palette_layout.get_widget(1, 0) 

3656 palette_widget = palette_layout.get_widget(2, 0) 

3657 spacer.set_horizontal(0.5*cm) 

3658 palette_widget.set_horizontal(0.5*cm) 

3659 

3660 if pm.endswith('+') or self.eps_mode: 

3661 outer = CenterLayout() 

3662 outer.set_policy((w, h), (0., 0.)) 

3663 inner = FrameLayout() 

3664 outer.set_widget(inner) 

3665 if with_palette: 

3666 inner.set_widget('center', palette_layout) 

3667 widget = palette_layout 

3668 else: 

3669 widget = inner.get_widget('center') 

3670 widget.set_policy((w/golden_ratio, 0.), (0., 0.), 

3671 aspect=1./golden_ratio) 

3672 mw = 3.0*cm 

3673 inner.set_fixed_margins( 

3674 mw, mw, mw/golden_ratio, mw/golden_ratio) 

3675 self.layout = inner 

3676 

3677 elif pm.startswith('custom_'): 

3678 layout = FrameLayout() 

3679 layout.set_policy((w, h), (0., 0.)) 

3680 mw = 3.0*cm 

3681 layout.set_min_margins( 

3682 mw, mw, mw/golden_ratio, mw/golden_ratio) 

3683 if with_palette: 

3684 layout.set_widget('center', palette_layout) 

3685 self.layout = layout 

3686 else: 

3687 outer = FrameLayout() 

3688 outer.set_policy((w, h), (0., 0.)) 

3689 outer.set_fixed_margins(1.*cm, 1.*cm, 1.*cm, 1.*cm) 

3690 

3691 inner = FrameLayout() 

3692 outer.set_widget('center', inner) 

3693 mw = 3.0*cm 

3694 inner.set_min_margins(mw, mw, mw/golden_ratio, mw/golden_ratio) 

3695 if with_palette: 

3696 inner.set_widget('center', palette_layout) 

3697 widget = palette_layout 

3698 else: 

3699 widget = inner.get_widget('center') 

3700 

3701 widget.set_aspect(1./golden_ratio) 

3702 

3703 self.layout = inner 

3704 

3705 return self.layout 

3706 

3707 def draw_layout(self, layout): 

3708 ''' 

3709 Use psxy to draw layout; for debugging 

3710 ''' 

3711 

3712 # corners = layout.get_corners(descend=True) 

3713 rects = num.array(layout.get_sizes(), dtype=float) 

3714 rects_wid = rects[:, 0, 0] 

3715 rects_hei = rects[:, 0, 1] 

3716 rects_center_x = rects[:, 1, 0] + rects_wid*0.5 

3717 rects_center_y = rects[:, 1, 1] + rects_hei*0.5 

3718 nrects = len(rects) 

3719 prects = (rects_center_x, rects_center_y, num.arange(nrects), 

3720 num.zeros(nrects), rects_hei, rects_wid) 

3721 

3722 # points = num.array(corners, dtype=float) 

3723 

3724 cptfile = self.tempfilename() + '.cpt' 

3725 self.makecpt( 

3726 C='ocean', 

3727 T='%g/%g/%g' % (-nrects, nrects, 1), 

3728 Z=True, 

3729 out_filename=cptfile, suppress_defaults=True) 

3730 

3731 bb = layout.bbox() 

3732 self.psxy( 

3733 in_columns=prects, 

3734 C=cptfile, 

3735 W='1p', 

3736 S='J', 

3737 R=(bb[0], bb[2], bb[1], bb[3]), 

3738 *layout.XYJ()) 

3739 

3740 

3741def simpleconf_to_ax(conf, axname): 

3742 c = {} 

3743 x = axname 

3744 for x in ('', axname): 

3745 for k in ('label', 'unit', 'scaled_unit', 'scaled_unit_factor', 

3746 'space', 'mode', 'approx_ticks', 'limits', 'masking', 'inc', 

3747 'snap'): 

3748 

3749 if x+k in conf: 

3750 c[k] = conf[x+k] 

3751 

3752 return Ax(**c) 

3753 

3754 

3755class DensityPlotDef(object): 

3756 def __init__(self, data, cpt='ocean', tension=0.7, size=(640, 480), 

3757 contour=False, method='surface', zscaler=None, **extra): 

3758 self.data = data 

3759 self.cpt = cpt 

3760 self.tension = tension 

3761 self.size = size 

3762 self.contour = contour 

3763 self.method = method 

3764 self.zscaler = zscaler 

3765 self.extra = extra 

3766 

3767 

3768class TextDef(object): 

3769 def __init__( 

3770 self, 

3771 data, 

3772 size=9, 

3773 justify='MC', 

3774 fontno=0, 

3775 offset=(0, 0), 

3776 color='black'): 

3777 

3778 self.data = data 

3779 self.size = size 

3780 self.justify = justify 

3781 self.fontno = fontno 

3782 self.offset = offset 

3783 self.color = color 

3784 

3785 

3786class Simple(object): 

3787 def __init__(self, gmtconfig=None, gmtversion='newest', **simple_config): 

3788 self.data = [] 

3789 self.symbols = [] 

3790 self.config = copy.deepcopy(simple_config) 

3791 self.gmtconfig = gmtconfig 

3792 self.density_plot_defs = [] 

3793 self.text_defs = [] 

3794 

3795 self.gmtversion = gmtversion 

3796 

3797 self.data_x = [] 

3798 self.symbols_x = [] 

3799 

3800 self.data_y = [] 

3801 self.symbols_y = [] 

3802 

3803 self.default_config = {} 

3804 self.set_defaults(width=15.*cm, 

3805 height=15.*cm / golden_ratio, 

3806 margins=(2.*cm, 2.*cm, 2.*cm, 2.*cm), 

3807 with_palette=False, 

3808 palette_offset=0.5*cm, 

3809 palette_width=None, 

3810 palette_height=None, 

3811 zlabeloffset=2*cm, 

3812 draw_layout=False) 

3813 

3814 self.setup_defaults() 

3815 self.fixate_widget_aspect = False 

3816 

3817 def setup_defaults(self): 

3818 pass 

3819 

3820 def set_defaults(self, **kwargs): 

3821 self.default_config.update(kwargs) 

3822 

3823 def plot(self, data, symbol=''): 

3824 self.data.append(data) 

3825 self.symbols.append(symbol) 

3826 

3827 def density_plot(self, data, **kwargs): 

3828 dpd = DensityPlotDef(data, **kwargs) 

3829 self.density_plot_defs.append(dpd) 

3830 

3831 def text(self, data, **kwargs): 

3832 dpd = TextDef(data, **kwargs) 

3833 self.text_defs.append(dpd) 

3834 

3835 def plot_x(self, data, symbol=''): 

3836 self.data_x.append(data) 

3837 self.symbols_x.append(symbol) 

3838 

3839 def plot_y(self, data, symbol=''): 

3840 self.data_y.append(data) 

3841 self.symbols_y.append(symbol) 

3842 

3843 def set(self, **kwargs): 

3844 self.config.update(kwargs) 

3845 

3846 def setup_base(self, conf): 

3847 w = conf.pop('width') 

3848 h = conf.pop('height') 

3849 margins = conf.pop('margins') 

3850 

3851 gmtconfig = {} 

3852 if self.gmtconfig is not None: 

3853 gmtconfig.update(self.gmtconfig) 

3854 

3855 gmt = GMT( 

3856 version=self.gmtversion, 

3857 config=gmtconfig, 

3858 config_papersize='Custom_%ix%i' % (w, h)) 

3859 

3860 layout = gmt.default_layout(with_palette=conf['with_palette']) 

3861 layout.set_min_margins(*margins) 

3862 if conf['with_palette']: 

3863 widget = layout.get_widget().get_widget(0, 0) 

3864 spacer = layout.get_widget().get_widget(1, 0) 

3865 spacer.set_horizontal(conf['palette_offset']) 

3866 palette_widget = layout.get_widget().get_widget(2, 0) 

3867 if conf['palette_width'] is not None: 

3868 palette_widget.set_horizontal(conf['palette_width']) 

3869 if conf['palette_height'] is not None: 

3870 palette_widget.set_vertical(conf['palette_height']) 

3871 widget.set_vertical(h-margins[2]-margins[3]-0.03*cm) 

3872 return gmt, layout, widget, palette_widget 

3873 else: 

3874 widget = layout.get_widget() 

3875 return gmt, layout, widget, None 

3876 

3877 def setup_projection(self, widget, scaler, conf): 

3878 pass 

3879 

3880 def setup_scaling(self, conf): 

3881 ndims = 2 

3882 if self.density_plot_defs: 

3883 ndims = 3 

3884 

3885 axes = [simpleconf_to_ax(conf, x) for x in 'xyz'[:ndims]] 

3886 

3887 data_all = [] 

3888 data_all.extend(self.data) 

3889 for dsd in self.density_plot_defs: 

3890 if dsd.zscaler is None: 

3891 data_all.append(dsd.data) 

3892 else: 

3893 data_all.append(dsd.data[:2]) 

3894 data_chopped = [ds[:ndims] for ds in data_all] 

3895 

3896 scaler = ScaleGuru(data_chopped, axes=axes[:ndims]) 

3897 

3898 self.setup_scaling_plus(scaler, axes[:ndims]) 

3899 

3900 return scaler 

3901 

3902 def setup_scaling_plus(self, scaler, axes): 

3903 pass 

3904 

3905 def setup_scaling_extra(self, scaler, conf): 

3906 

3907 scaler_x = scaler.copy() 

3908 scaler_x.data_ranges[1] = (0., 1.) 

3909 scaler_x.axes[1].mode = 'off' 

3910 

3911 scaler_y = scaler.copy() 

3912 scaler_y.data_ranges[0] = (0., 1.) 

3913 scaler_y.axes[0].mode = 'off' 

3914 

3915 return scaler_x, scaler_y 

3916 

3917 def draw_density(self, gmt, widget, scaler): 

3918 

3919 R = scaler.R() 

3920 # par = scaler.get_params() 

3921 rxyj = R + widget.XYJ() 

3922 innerticks = False 

3923 for dpd in self.density_plot_defs: 

3924 

3925 fn_cpt = gmt.tempfilename() + '.cpt' 

3926 

3927 if dpd.zscaler is not None: 

3928 s = dpd.zscaler 

3929 else: 

3930 s = scaler 

3931 

3932 gmt.makecpt(C=dpd.cpt, out_filename=fn_cpt, *s.T()) 

3933 

3934 fn_grid = gmt.tempfilename() 

3935 

3936 fn_mean = gmt.tempfilename() 

3937 

3938 if dpd.method in ('surface', 'triangulate'): 

3939 gmt.blockmean(in_columns=dpd.data, 

3940 I='%i+/%i+' % dpd.size, # noqa 

3941 out_filename=fn_mean, *R) 

3942 

3943 if dpd.method == 'surface': 

3944 gmt.surface( 

3945 in_filename=fn_mean, 

3946 T=dpd.tension, 

3947 G=fn_grid, 

3948 I='%i+/%i+' % dpd.size, # noqa 

3949 out_discard=True, 

3950 *R) 

3951 

3952 if dpd.method == 'triangulate': 

3953 gmt.triangulate( 

3954 in_filename=fn_mean, 

3955 G=fn_grid, 

3956 I='%i+/%i+' % dpd.size, # noqa 

3957 out_discard=True, 

3958 V=True, 

3959 *R) 

3960 

3961 if gmt.is_gmt5(): 

3962 gmt.grdimage(fn_grid, C=fn_cpt, E='i', n='l', *rxyj) 

3963 

3964 else: 

3965 gmt.grdimage(fn_grid, C=fn_cpt, E='i', S='l', *rxyj) 

3966 

3967 if dpd.contour: 

3968 gmt.grdcontour(fn_grid, C=fn_cpt, W='0.5p,black', *rxyj) 

3969 innerticks = '0.5p,black' 

3970 

3971 os.remove(fn_grid) 

3972 os.remove(fn_mean) 

3973 

3974 if dpd.method == 'fillcontour': 

3975 extra = dict(C=fn_cpt) 

3976 extra.update(dpd.extra) 

3977 gmt.pscontour(in_columns=dpd.data, 

3978 I=True, *rxyj, **extra) # noqa 

3979 

3980 if dpd.method == 'contour': 

3981 extra = dict(W='0.5p,black', C=fn_cpt) 

3982 extra.update(dpd.extra) 

3983 gmt.pscontour(in_columns=dpd.data, *rxyj, **extra) 

3984 

3985 return fn_cpt, innerticks 

3986 

3987 def draw_basemap(self, gmt, widget, scaler): 

3988 gmt.psbasemap(*(widget.JXY() + scaler.RB(ax_projection=True))) 

3989 

3990 def draw(self, gmt, widget, scaler): 

3991 rxyj = scaler.R() + widget.JXY() 

3992 for dat, sym in zip(self.data, self.symbols): 

3993 gmt.psxy(in_columns=dat, *(sym.split()+rxyj)) 

3994 

3995 def post_draw(self, gmt, widget, scaler): 

3996 pass 

3997 

3998 def pre_draw(self, gmt, widget, scaler): 

3999 pass 

4000 

4001 def draw_extra(self, gmt, widget, scaler_x, scaler_y): 

4002 

4003 for dat, sym in zip(self.data_x, self.symbols_x): 

4004 gmt.psxy(in_columns=dat, 

4005 *(sym.split() + scaler_x.R() + widget.JXY())) 

4006 

4007 for dat, sym in zip(self.data_y, self.symbols_y): 

4008 gmt.psxy(in_columns=dat, 

4009 *(sym.split() + scaler_y.R() + widget.JXY())) 

4010 

4011 def draw_text(self, gmt, widget, scaler): 

4012 

4013 rxyj = scaler.R() + widget.JXY() 

4014 for td in self.text_defs: 

4015 x, y = td.data[0:2] 

4016 text = td.data[-1] 

4017 size = td.size 

4018 angle = 0 

4019 fontno = td.fontno 

4020 justify = td.justify 

4021 color = td.color 

4022 if gmt.is_gmt5(): 

4023 gmt.pstext( 

4024 in_rows=[(x, y, text)], 

4025 F='+f%gp,%s,%s+a%g+j%s' % ( 

4026 size, fontno, color, angle, justify), 

4027 D='%gp/%gp' % td.offset, *rxyj) 

4028 else: 

4029 gmt.pstext( 

4030 in_rows=[(x, y, size, angle, fontno, justify, text)], 

4031 D='%gp/%gp' % td.offset, *rxyj) 

4032 

4033 def save(self, filename, resolution=150): 

4034 

4035 conf = dict(self.default_config) 

4036 conf.update(self.config) 

4037 

4038 gmt, layout, widget, palette_widget = self.setup_base(conf) 

4039 scaler = self.setup_scaling(conf) 

4040 scaler_x, scaler_y = self.setup_scaling_extra(scaler, conf) 

4041 

4042 self.setup_projection(widget, scaler, conf) 

4043 if self.fixate_widget_aspect: 

4044 aspect = aspect_for_projection( 

4045 gmt.installation['version'], *(widget.J() + scaler.R())) 

4046 

4047 widget.set_aspect(aspect) 

4048 

4049 if conf['draw_layout']: 

4050 gmt.draw_layout(layout) 

4051 cptfile = None 

4052 if self.density_plot_defs: 

4053 cptfile, innerticks = self.draw_density(gmt, widget, scaler) 

4054 self.pre_draw(gmt, widget, scaler) 

4055 self.draw(gmt, widget, scaler) 

4056 self.post_draw(gmt, widget, scaler) 

4057 self.draw_extra(gmt, widget, scaler_x, scaler_y) 

4058 self.draw_text(gmt, widget, scaler) 

4059 self.draw_basemap(gmt, widget, scaler) 

4060 

4061 if palette_widget and cptfile: 

4062 nice_palette(gmt, palette_widget, scaler, cptfile, 

4063 innerticks=innerticks, 

4064 zlabeloffset=conf['zlabeloffset']) 

4065 

4066 gmt.save(filename, resolution=resolution) 

4067 

4068 

4069class LinLinPlot(Simple): 

4070 pass 

4071 

4072 

4073class LogLinPlot(Simple): 

4074 

4075 def setup_defaults(self): 

4076 self.set_defaults(xmode='min-max') 

4077 

4078 def setup_projection(self, widget, scaler, conf): 

4079 widget['J'] = '-JX%(width)gpl/%(height)gp' 

4080 scaler['B'] = '-B2:%(xlabel)s:/%(yinc)g:%(ylabel)s:WSen' 

4081 

4082 

4083class LinLogPlot(Simple): 

4084 

4085 def setup_defaults(self): 

4086 self.set_defaults(ymode='min-max') 

4087 

4088 def setup_projection(self, widget, scaler, conf): 

4089 widget['J'] = '-JX%(width)gp/%(height)gpl' 

4090 scaler['B'] = '-B%(xinc)g:%(xlabel)s:/2:%(ylabel)s:WSen' 

4091 

4092 

4093class LogLogPlot(Simple): 

4094 

4095 def setup_defaults(self): 

4096 self.set_defaults(mode='min-max') 

4097 

4098 def setup_projection(self, widget, scaler, conf): 

4099 widget['J'] = '-JX%(width)gpl/%(height)gpl' 

4100 scaler['B'] = '-B2:%(xlabel)s:/2:%(ylabel)s:WSen' 

4101 

4102 

4103class AziDistPlot(Simple): 

4104 

4105 def __init__(self, *args, **kwargs): 

4106 Simple.__init__(self, *args, **kwargs) 

4107 self.fixate_widget_aspect = True 

4108 

4109 def setup_defaults(self): 

4110 self.set_defaults( 

4111 height=15.*cm, 

4112 width=15.*cm, 

4113 xmode='off', 

4114 xlimits=(0., 360.), 

4115 xinc=45.) 

4116 

4117 def setup_projection(self, widget, scaler, conf): 

4118 widget['J'] = '-JPa%(width)gp' 

4119 

4120 def setup_scaling_plus(self, scaler, axes): 

4121 scaler['B'] = '-B%(xinc)g:%(xlabel)s:/%(yinc)g:%(ylabel)s:N' 

4122 

4123 

4124class MPlot(Simple): 

4125 

4126 def __init__(self, *args, **kwargs): 

4127 Simple.__init__(self, *args, **kwargs) 

4128 self.fixate_widget_aspect = True 

4129 

4130 def setup_defaults(self): 

4131 self.set_defaults(xmode='min-max', ymode='min-max') 

4132 

4133 def setup_projection(self, widget, scaler, conf): 

4134 par = scaler.get_params() 

4135 lon0 = (par['xmin'] + par['xmax'])/2. 

4136 lat0 = (par['ymin'] + par['ymax'])/2. 

4137 sll = '%g/%g' % (lon0, lat0) 

4138 widget['J'] = '-JM' + sll + '/%(width)gp' 

4139 scaler['B'] = \ 

4140 '-B%(xinc)gg%(xinc)g:%(xlabel)s:/%(yinc)gg%(yinc)g:%(ylabel)s:WSen' 

4141 

4142 

4143def nice_palette(gmt, widget, scaleguru, cptfile, zlabeloffset=0.8*inch, 

4144 innerticks=True): 

4145 

4146 par = scaleguru.get_params() 

4147 par_ax = scaleguru.get_params(ax_projection=True) 

4148 nz_palette = int(widget.height()/inch * 300) 

4149 px = num.zeros(nz_palette*2) 

4150 px[1::2] += 1 

4151 pz = num.linspace(par['zmin'], par['zmax'], nz_palette).repeat(2) 

4152 pdz = pz[2]-pz[0] 

4153 palgrdfile = gmt.tempfilename() 

4154 pal_r = (0, 1, par['zmin'], par['zmax']) 

4155 pal_ax_r = (0, 1, par_ax['zmin'], par_ax['zmax']) 

4156 gmt.xyz2grd( 

4157 G=palgrdfile, R=pal_r, 

4158 I=(1, pdz), in_columns=(px, pz, pz), # noqa 

4159 out_discard=True) 

4160 

4161 gmt.grdimage(palgrdfile, R=pal_r, C=cptfile, *widget.JXY()) 

4162 if isinstance(innerticks, str): 

4163 tickpen = innerticks 

4164 gmt.grdcontour(palgrdfile, W=tickpen, R=pal_r, C=cptfile, 

4165 *widget.JXY()) 

4166 

4167 negpalwid = '%gp' % -widget.width() 

4168 if not isinstance(innerticks, str) and innerticks: 

4169 ticklen = negpalwid 

4170 else: 

4171 ticklen = '0p' 

4172 

4173 TICK_LENGTH_PARAM = 'MAP_TICK_LENGTH' if gmt.is_gmt5() else 'TICK_LENGTH' 

4174 gmt.psbasemap( 

4175 R=pal_ax_r, B='4::/%(zinc)g::nsw' % par_ax, 

4176 config={TICK_LENGTH_PARAM: ticklen}, 

4177 *widget.JXY()) 

4178 

4179 if innerticks: 

4180 gmt.psbasemap( 

4181 R=pal_ax_r, B='4::/%(zinc)g::E' % par_ax, 

4182 config={TICK_LENGTH_PARAM: '0p'}, 

4183 *widget.JXY()) 

4184 else: 

4185 gmt.psbasemap(R=pal_ax_r, B='4::/%(zinc)g::E' % par_ax, *widget.JXY()) 

4186 

4187 if par_ax['zlabel']: 

4188 label_font = gmt.label_font() 

4189 label_font_size = gmt.label_font_size() 

4190 label_offset = zlabeloffset 

4191 gmt.pstext( 

4192 R=(0, 1, 0, 2), D='%gp/0p' % label_offset, 

4193 N=True, 

4194 in_rows=[(1, 1, label_font_size, -90, label_font, 'CB', 

4195 par_ax['zlabel'])], 

4196 *widget.JXY())