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 is part of GmtPy (http://emolch.github.io/gmtpy/) 

10# See there for copying and licensing information. 

11 

12from __future__ import print_function, absolute_import 

13import subprocess 

14try: 

15 from StringIO import StringIO as BytesIO 

16except ImportError: 

17 from io import BytesIO 

18import re 

19import os 

20import sys 

21import shutil 

22from os.path import join as pjoin 

23import tempfile 

24import random 

25import logging 

26import math 

27import numpy as num 

28import copy 

29from select import select 

30from scipy.io import netcdf 

31 

32from pyrocko import ExternalProgramMissing 

33from . import AutoScaler 

34 

35try: 

36 newstr = unicode 

37except NameError: 

38 newstr = str 

39 

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

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

42 

43 

44encoding_gmt_to_python = { 

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

46 'standard+': 'ascii', 

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

48 'standard': 'ascii'} 

49 

50for i in range(1, 11): 

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

52 

53 

54def have_gmt(): 

55 try: 

56 get_gmt_installation('newest') 

57 return True 

58 

59 except GMTInstallationProblem: 

60 return False 

61 

62 

63def check_have_gmt(): 

64 if not have_gmt(): 

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

66 

67 

68def have_pixmaptools(): 

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

70 try: 

71 p = subprocess.Popen( 

72 prog, 

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

74 

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

76 

77 except OSError: 

78 return False 

79 

80 return True 

81 

82 

83class GmtPyError(Exception): 

84 pass 

85 

86 

87class GMTError(GmtPyError): 

88 pass 

89 

90 

91class GMTInstallationProblem(GmtPyError): 

92 pass 

93 

94 

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

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

97 

98 _, tmp_filename_base = tempfile.mkstemp() 

99 

100 try: 

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

102 fmt_arg = '-svg' 

103 tmp_filename = tmp_filename_base 

104 oversample = 1.0 

105 else: 

106 fmt_arg = '-png' 

107 tmp_filename = tmp_filename_base + '-1.png' 

108 

109 if size is not None: 

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

111 elif width is not None: 

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

113 elif height is not None: 

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

115 else: 

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

117 

118 try: 

119 subprocess.check_call( 

120 ['pdftocairo'] + scale_args + 

121 [fmt_arg, in_filename, tmp_filename_base]) 

122 except OSError as e: 

123 raise GmtPyError( 

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

125 

126 if oversample > 1.: 

127 try: 

128 subprocess.check_call([ 

129 'convert', 

130 tmp_filename, 

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

132 out_filename]) 

133 except OSError as e: 

134 raise GmtPyError( 

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

136 

137 else: 

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

139 shutil.move(tmp_filename, out_filename) 

140 else: 

141 try: 

142 subprocess.check_call( 

143 ['convert', tmp_filename, out_filename]) 

144 except Exception as e: 

145 raise GmtPyError( 

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

147 % str(e)) 

148 

149 except Exception: 

150 raise 

151 

152 finally: 

153 if os.path.exists(tmp_filename_base): 

154 os.remove(tmp_filename_base) 

155 

156 if os.path.exists(tmp_filename): 

157 os.remove(tmp_filename) 

158 

159 

160def get_bbox(s): 

161 for pat in [find_hiresbb, find_bb]: 

162 m = pat.search(s) 

163 if m: 

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

165 return bb 

166 

167 raise GmtPyError('Cannot find bbox') 

168 

169 

170def replace_bbox(bbox, *args): 

171 

172 def repl(m): 

173 if m.group(1): 

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

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

176 else: 

177 return ('%%%%BoundingBox: %i %i %i %i' % ( 

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

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

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

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

182 

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

184 if len(args) == 1: 

185 s = args[0] 

186 return pat.sub(repl, s) 

187 

188 else: 

189 fin, fout = args 

190 nn = 0 

191 for line in fin: 

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

193 nn += n 

194 fout.write(line) 

195 if nn == 2: 

196 break 

197 

198 if nn == 2: 

199 for line in fin: 

200 fout.write(line) 

201 

202 

203def escape_shell_arg(s): 

204 ''' 

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

206 insecure. 

207 ''' 

208 

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

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

211 else: 

212 return s 

213 

214 

215def escape_shell_args(args): 

216 ''' 

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

218 insecure. 

219 ''' 

220 

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

222 

223 

224golden_ratio = 1.61803 

225 

226# units in points 

227_units = { 

228 'i': 72., 

229 'c': 72./2.54, 

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

231 'p': 1.} 

232 

233inch = _units['i'] 

234cm = _units['c'] 

235 

236# some awsome colors 

237tango_colors = { 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

265} 

266 

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

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

269 'butter2')] 

270 

271 

272def color(x=None): 

273 ''' 

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

275 

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

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

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

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

280 transformed into the string form which GMT expects. 

281 ''' 

282 

283 if x is None: 

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

285 

286 if isinstance(x, int): 

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

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

289 else: 

290 return '0/0/0' 

291 

292 elif isinstance(x, str): 

293 if x in tango_colors: 

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

295 else: 

296 return x 

297 

298 return '%i/%i/%i' % x 

299 

300 

301def color_tup(x=None): 

302 if x is None: 

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

304 

305 if isinstance(x, int): 

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

307 return graph_colors[x] 

308 else: 

309 return (0, 0, 0) 

310 

311 elif isinstance(x, str): 

312 if x in tango_colors: 

313 return tango_colors[x] 

314 

315 return x 

316 

317 

318_gmt_installations = {} 

319 

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

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

322 

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

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

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

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

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

328# 'bin': '/usr/bin' } 

329 

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

331 

332 

333def key_version(a): 

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

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

336 

337 

338def newest_installed_gmt_version(): 

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

340 

341 

342def all_installed_gmt_versions(): 

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

344 

345 

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

347# changed. 

348 

349_gmt_defaults_by_version = {} 

350_gmt_defaults_by_version['4.2.1'] = r''' 

351# 

352# GMT-SYSTEM 4.2.1 Defaults file 

353# 

354#-------- Plot Media Parameters ------------- 

355PAGE_COLOR = 255/255/255 

356PAGE_ORIENTATION = portrait 

357PAPER_MEDIA = a4+ 

358#-------- Basemap Annotation Parameters ------ 

359ANNOT_MIN_ANGLE = 20 

360ANNOT_MIN_SPACING = 0 

361ANNOT_FONT_PRIMARY = Helvetica 

362ANNOT_FONT_SIZE = 12p 

363ANNOT_OFFSET_PRIMARY = 0.075i 

364ANNOT_FONT_SECONDARY = Helvetica 

365ANNOT_FONT_SIZE_SECONDARY = 16p 

366ANNOT_OFFSET_SECONDARY = 0.075i 

367DEGREE_SYMBOL = ring 

368HEADER_FONT = Helvetica 

369HEADER_FONT_SIZE = 36p 

370HEADER_OFFSET = 0.1875i 

371LABEL_FONT = Helvetica 

372LABEL_FONT_SIZE = 14p 

373LABEL_OFFSET = 0.1125i 

374OBLIQUE_ANNOTATION = 1 

375PLOT_CLOCK_FORMAT = hh:mm:ss 

376PLOT_DATE_FORMAT = yyyy-mm-dd 

377PLOT_DEGREE_FORMAT = +ddd:mm:ss 

378Y_AXIS_TYPE = hor_text 

379#-------- Basemap Layout Parameters --------- 

380BASEMAP_AXES = WESN 

381BASEMAP_FRAME_RGB = 0/0/0 

382BASEMAP_TYPE = plain 

383FRAME_PEN = 1.25p 

384FRAME_WIDTH = 0.075i 

385GRID_CROSS_SIZE_PRIMARY = 0i 

386GRID_CROSS_SIZE_SECONDARY = 0i 

387GRID_PEN_PRIMARY = 0.25p 

388GRID_PEN_SECONDARY = 0.5p 

389MAP_SCALE_HEIGHT = 0.075i 

390TICK_LENGTH = 0.075i 

391POLAR_CAP = 85/90 

392TICK_PEN = 0.5p 

393X_AXIS_LENGTH = 9i 

394Y_AXIS_LENGTH = 6i 

395X_ORIGIN = 1i 

396Y_ORIGIN = 1i 

397UNIX_TIME = FALSE 

398UNIX_TIME_POS = -0.75i/-0.75i 

399#-------- Color System Parameters ----------- 

400COLOR_BACKGROUND = 0/0/0 

401COLOR_FOREGROUND = 255/255/255 

402COLOR_NAN = 128/128/128 

403COLOR_IMAGE = adobe 

404COLOR_MODEL = rgb 

405HSV_MIN_SATURATION = 1 

406HSV_MAX_SATURATION = 0.1 

407HSV_MIN_VALUE = 0.3 

408HSV_MAX_VALUE = 1 

409#-------- PostScript Parameters ------------- 

410CHAR_ENCODING = ISOLatin1+ 

411DOTS_PR_INCH = 300 

412N_COPIES = 1 

413PS_COLOR = rgb 

414PS_IMAGE_COMPRESS = none 

415PS_IMAGE_FORMAT = ascii 

416PS_LINE_CAP = round 

417PS_LINE_JOIN = miter 

418PS_MITER_LIMIT = 35 

419PS_VERBOSE = FALSE 

420GLOBAL_X_SCALE = 1 

421GLOBAL_Y_SCALE = 1 

422#-------- I/O Format Parameters ------------- 

423D_FORMAT = %lg 

424FIELD_DELIMITER = tab 

425GRIDFILE_SHORTHAND = FALSE 

426GRID_FORMAT = nf 

427INPUT_CLOCK_FORMAT = hh:mm:ss 

428INPUT_DATE_FORMAT = yyyy-mm-dd 

429IO_HEADER = FALSE 

430N_HEADER_RECS = 1 

431OUTPUT_CLOCK_FORMAT = hh:mm:ss 

432OUTPUT_DATE_FORMAT = yyyy-mm-dd 

433OUTPUT_DEGREE_FORMAT = +D 

434XY_TOGGLE = FALSE 

435#-------- Projection Parameters ------------- 

436ELLIPSOID = WGS-84 

437MAP_SCALE_FACTOR = default 

438MEASURE_UNIT = inch 

439#-------- Calendar/Time Parameters ---------- 

440TIME_FORMAT_PRIMARY = full 

441TIME_FORMAT_SECONDARY = full 

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

443TIME_IS_INTERVAL = OFF 

444TIME_INTERVAL_FRACTION = 0.5 

445TIME_LANGUAGE = us 

446TIME_SYSTEM = other 

447TIME_UNIT = d 

448TIME_WEEK_START = Sunday 

449Y2K_OFFSET_YEAR = 1950 

450#-------- Miscellaneous Parameters ---------- 

451HISTORY = TRUE 

452INTERPOLANT = akima 

453LINE_STEP = 0.01i 

454VECTOR_SHAPE = 0 

455VERBOSE = FALSE''' 

456 

457_gmt_defaults_by_version['4.3.0'] = r''' 

458# 

459# GMT-SYSTEM 4.3.0 Defaults file 

460# 

461#-------- Plot Media Parameters ------------- 

462PAGE_COLOR = 255/255/255 

463PAGE_ORIENTATION = portrait 

464PAPER_MEDIA = a4+ 

465#-------- Basemap Annotation Parameters ------ 

466ANNOT_MIN_ANGLE = 20 

467ANNOT_MIN_SPACING = 0 

468ANNOT_FONT_PRIMARY = Helvetica 

469ANNOT_FONT_SIZE_PRIMARY = 12p 

470ANNOT_OFFSET_PRIMARY = 0.075i 

471ANNOT_FONT_SECONDARY = Helvetica 

472ANNOT_FONT_SIZE_SECONDARY = 16p 

473ANNOT_OFFSET_SECONDARY = 0.075i 

474DEGREE_SYMBOL = ring 

475HEADER_FONT = Helvetica 

476HEADER_FONT_SIZE = 36p 

477HEADER_OFFSET = 0.1875i 

478LABEL_FONT = Helvetica 

479LABEL_FONT_SIZE = 14p 

480LABEL_OFFSET = 0.1125i 

481OBLIQUE_ANNOTATION = 1 

482PLOT_CLOCK_FORMAT = hh:mm:ss 

483PLOT_DATE_FORMAT = yyyy-mm-dd 

484PLOT_DEGREE_FORMAT = +ddd:mm:ss 

485Y_AXIS_TYPE = hor_text 

486#-------- Basemap Layout Parameters --------- 

487BASEMAP_AXES = WESN 

488BASEMAP_FRAME_RGB = 0/0/0 

489BASEMAP_TYPE = plain 

490FRAME_PEN = 1.25p 

491FRAME_WIDTH = 0.075i 

492GRID_CROSS_SIZE_PRIMARY = 0i 

493GRID_PEN_PRIMARY = 0.25p 

494GRID_CROSS_SIZE_SECONDARY = 0i 

495GRID_PEN_SECONDARY = 0.5p 

496MAP_SCALE_HEIGHT = 0.075i 

497POLAR_CAP = 85/90 

498TICK_LENGTH = 0.075i 

499TICK_PEN = 0.5p 

500X_AXIS_LENGTH = 9i 

501Y_AXIS_LENGTH = 6i 

502X_ORIGIN = 1i 

503Y_ORIGIN = 1i 

504UNIX_TIME = FALSE 

505UNIX_TIME_POS = BL/-0.75i/-0.75i 

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

507#-------- Color System Parameters ----------- 

508COLOR_BACKGROUND = 0/0/0 

509COLOR_FOREGROUND = 255/255/255 

510COLOR_NAN = 128/128/128 

511COLOR_IMAGE = adobe 

512COLOR_MODEL = rgb 

513HSV_MIN_SATURATION = 1 

514HSV_MAX_SATURATION = 0.1 

515HSV_MIN_VALUE = 0.3 

516HSV_MAX_VALUE = 1 

517#-------- PostScript Parameters ------------- 

518CHAR_ENCODING = ISOLatin1+ 

519DOTS_PR_INCH = 300 

520N_COPIES = 1 

521PS_COLOR = rgb 

522PS_IMAGE_COMPRESS = none 

523PS_IMAGE_FORMAT = ascii 

524PS_LINE_CAP = round 

525PS_LINE_JOIN = miter 

526PS_MITER_LIMIT = 35 

527PS_VERBOSE = FALSE 

528GLOBAL_X_SCALE = 1 

529GLOBAL_Y_SCALE = 1 

530#-------- I/O Format Parameters ------------- 

531D_FORMAT = %lg 

532FIELD_DELIMITER = tab 

533GRIDFILE_SHORTHAND = FALSE 

534GRID_FORMAT = nf 

535INPUT_CLOCK_FORMAT = hh:mm:ss 

536INPUT_DATE_FORMAT = yyyy-mm-dd 

537IO_HEADER = FALSE 

538N_HEADER_RECS = 1 

539OUTPUT_CLOCK_FORMAT = hh:mm:ss 

540OUTPUT_DATE_FORMAT = yyyy-mm-dd 

541OUTPUT_DEGREE_FORMAT = +D 

542XY_TOGGLE = FALSE 

543#-------- Projection Parameters ------------- 

544ELLIPSOID = WGS-84 

545MAP_SCALE_FACTOR = default 

546MEASURE_UNIT = inch 

547#-------- Calendar/Time Parameters ---------- 

548TIME_FORMAT_PRIMARY = full 

549TIME_FORMAT_SECONDARY = full 

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

551TIME_IS_INTERVAL = OFF 

552TIME_INTERVAL_FRACTION = 0.5 

553TIME_LANGUAGE = us 

554TIME_UNIT = d 

555TIME_WEEK_START = Sunday 

556Y2K_OFFSET_YEAR = 1950 

557#-------- Miscellaneous Parameters ---------- 

558HISTORY = TRUE 

559INTERPOLANT = akima 

560LINE_STEP = 0.01i 

561VECTOR_SHAPE = 0 

562VERBOSE = FALSE''' 

563 

564 

565_gmt_defaults_by_version['4.3.1'] = r''' 

566# 

567# GMT-SYSTEM 4.3.1 Defaults file 

568# 

569#-------- Plot Media Parameters ------------- 

570PAGE_COLOR = 255/255/255 

571PAGE_ORIENTATION = portrait 

572PAPER_MEDIA = a4+ 

573#-------- Basemap Annotation Parameters ------ 

574ANNOT_MIN_ANGLE = 20 

575ANNOT_MIN_SPACING = 0 

576ANNOT_FONT_PRIMARY = Helvetica 

577ANNOT_FONT_SIZE_PRIMARY = 12p 

578ANNOT_OFFSET_PRIMARY = 0.075i 

579ANNOT_FONT_SECONDARY = Helvetica 

580ANNOT_FONT_SIZE_SECONDARY = 16p 

581ANNOT_OFFSET_SECONDARY = 0.075i 

582DEGREE_SYMBOL = ring 

583HEADER_FONT = Helvetica 

584HEADER_FONT_SIZE = 36p 

585HEADER_OFFSET = 0.1875i 

586LABEL_FONT = Helvetica 

587LABEL_FONT_SIZE = 14p 

588LABEL_OFFSET = 0.1125i 

589OBLIQUE_ANNOTATION = 1 

590PLOT_CLOCK_FORMAT = hh:mm:ss 

591PLOT_DATE_FORMAT = yyyy-mm-dd 

592PLOT_DEGREE_FORMAT = +ddd:mm:ss 

593Y_AXIS_TYPE = hor_text 

594#-------- Basemap Layout Parameters --------- 

595BASEMAP_AXES = WESN 

596BASEMAP_FRAME_RGB = 0/0/0 

597BASEMAP_TYPE = plain 

598FRAME_PEN = 1.25p 

599FRAME_WIDTH = 0.075i 

600GRID_CROSS_SIZE_PRIMARY = 0i 

601GRID_PEN_PRIMARY = 0.25p 

602GRID_CROSS_SIZE_SECONDARY = 0i 

603GRID_PEN_SECONDARY = 0.5p 

604MAP_SCALE_HEIGHT = 0.075i 

605POLAR_CAP = 85/90 

606TICK_LENGTH = 0.075i 

607TICK_PEN = 0.5p 

608X_AXIS_LENGTH = 9i 

609Y_AXIS_LENGTH = 6i 

610X_ORIGIN = 1i 

611Y_ORIGIN = 1i 

612UNIX_TIME = FALSE 

613UNIX_TIME_POS = BL/-0.75i/-0.75i 

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

615#-------- Color System Parameters ----------- 

616COLOR_BACKGROUND = 0/0/0 

617COLOR_FOREGROUND = 255/255/255 

618COLOR_NAN = 128/128/128 

619COLOR_IMAGE = adobe 

620COLOR_MODEL = rgb 

621HSV_MIN_SATURATION = 1 

622HSV_MAX_SATURATION = 0.1 

623HSV_MIN_VALUE = 0.3 

624HSV_MAX_VALUE = 1 

625#-------- PostScript Parameters ------------- 

626CHAR_ENCODING = ISOLatin1+ 

627DOTS_PR_INCH = 300 

628N_COPIES = 1 

629PS_COLOR = rgb 

630PS_IMAGE_COMPRESS = none 

631PS_IMAGE_FORMAT = ascii 

632PS_LINE_CAP = round 

633PS_LINE_JOIN = miter 

634PS_MITER_LIMIT = 35 

635PS_VERBOSE = FALSE 

636GLOBAL_X_SCALE = 1 

637GLOBAL_Y_SCALE = 1 

638#-------- I/O Format Parameters ------------- 

639D_FORMAT = %lg 

640FIELD_DELIMITER = tab 

641GRIDFILE_SHORTHAND = FALSE 

642GRID_FORMAT = nf 

643INPUT_CLOCK_FORMAT = hh:mm:ss 

644INPUT_DATE_FORMAT = yyyy-mm-dd 

645IO_HEADER = FALSE 

646N_HEADER_RECS = 1 

647OUTPUT_CLOCK_FORMAT = hh:mm:ss 

648OUTPUT_DATE_FORMAT = yyyy-mm-dd 

649OUTPUT_DEGREE_FORMAT = +D 

650XY_TOGGLE = FALSE 

651#-------- Projection Parameters ------------- 

652ELLIPSOID = WGS-84 

653MAP_SCALE_FACTOR = default 

654MEASURE_UNIT = inch 

655#-------- Calendar/Time Parameters ---------- 

656TIME_FORMAT_PRIMARY = full 

657TIME_FORMAT_SECONDARY = full 

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

659TIME_IS_INTERVAL = OFF 

660TIME_INTERVAL_FRACTION = 0.5 

661TIME_LANGUAGE = us 

662TIME_UNIT = d 

663TIME_WEEK_START = Sunday 

664Y2K_OFFSET_YEAR = 1950 

665#-------- Miscellaneous Parameters ---------- 

666HISTORY = TRUE 

667INTERPOLANT = akima 

668LINE_STEP = 0.01i 

669VECTOR_SHAPE = 0 

670VERBOSE = FALSE''' 

671 

672 

673_gmt_defaults_by_version['4.4.0'] = r''' 

674# 

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

676# 

677#-------- Plot Media Parameters ------------- 

678PAGE_COLOR = 255/255/255 

679PAGE_ORIENTATION = portrait 

680PAPER_MEDIA = a4+ 

681#-------- Basemap Annotation Parameters ------ 

682ANNOT_MIN_ANGLE = 20 

683ANNOT_MIN_SPACING = 0 

684ANNOT_FONT_PRIMARY = Helvetica 

685ANNOT_FONT_SIZE_PRIMARY = 14p 

686ANNOT_OFFSET_PRIMARY = 0.075i 

687ANNOT_FONT_SECONDARY = Helvetica 

688ANNOT_FONT_SIZE_SECONDARY = 16p 

689ANNOT_OFFSET_SECONDARY = 0.075i 

690DEGREE_SYMBOL = ring 

691HEADER_FONT = Helvetica 

692HEADER_FONT_SIZE = 36p 

693HEADER_OFFSET = 0.1875i 

694LABEL_FONT = Helvetica 

695LABEL_FONT_SIZE = 14p 

696LABEL_OFFSET = 0.1125i 

697OBLIQUE_ANNOTATION = 1 

698PLOT_CLOCK_FORMAT = hh:mm:ss 

699PLOT_DATE_FORMAT = yyyy-mm-dd 

700PLOT_DEGREE_FORMAT = +ddd:mm:ss 

701Y_AXIS_TYPE = hor_text 

702#-------- Basemap Layout Parameters --------- 

703BASEMAP_AXES = WESN 

704BASEMAP_FRAME_RGB = 0/0/0 

705BASEMAP_TYPE = plain 

706FRAME_PEN = 1.25p 

707FRAME_WIDTH = 0.075i 

708GRID_CROSS_SIZE_PRIMARY = 0i 

709GRID_PEN_PRIMARY = 0.25p 

710GRID_CROSS_SIZE_SECONDARY = 0i 

711GRID_PEN_SECONDARY = 0.5p 

712MAP_SCALE_HEIGHT = 0.075i 

713POLAR_CAP = 85/90 

714TICK_LENGTH = 0.075i 

715TICK_PEN = 0.5p 

716X_AXIS_LENGTH = 9i 

717Y_AXIS_LENGTH = 6i 

718X_ORIGIN = 1i 

719Y_ORIGIN = 1i 

720UNIX_TIME = FALSE 

721UNIX_TIME_POS = BL/-0.75i/-0.75i 

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

723#-------- Color System Parameters ----------- 

724COLOR_BACKGROUND = 0/0/0 

725COLOR_FOREGROUND = 255/255/255 

726COLOR_NAN = 128/128/128 

727COLOR_IMAGE = adobe 

728COLOR_MODEL = rgb 

729HSV_MIN_SATURATION = 1 

730HSV_MAX_SATURATION = 0.1 

731HSV_MIN_VALUE = 0.3 

732HSV_MAX_VALUE = 1 

733#-------- PostScript Parameters ------------- 

734CHAR_ENCODING = ISOLatin1+ 

735DOTS_PR_INCH = 300 

736N_COPIES = 1 

737PS_COLOR = rgb 

738PS_IMAGE_COMPRESS = lzw 

739PS_IMAGE_FORMAT = ascii 

740PS_LINE_CAP = round 

741PS_LINE_JOIN = miter 

742PS_MITER_LIMIT = 35 

743PS_VERBOSE = FALSE 

744GLOBAL_X_SCALE = 1 

745GLOBAL_Y_SCALE = 1 

746#-------- I/O Format Parameters ------------- 

747D_FORMAT = %lg 

748FIELD_DELIMITER = tab 

749GRIDFILE_SHORTHAND = FALSE 

750GRID_FORMAT = nf 

751INPUT_CLOCK_FORMAT = hh:mm:ss 

752INPUT_DATE_FORMAT = yyyy-mm-dd 

753IO_HEADER = FALSE 

754N_HEADER_RECS = 1 

755OUTPUT_CLOCK_FORMAT = hh:mm:ss 

756OUTPUT_DATE_FORMAT = yyyy-mm-dd 

757OUTPUT_DEGREE_FORMAT = +D 

758XY_TOGGLE = FALSE 

759#-------- Projection Parameters ------------- 

760ELLIPSOID = WGS-84 

761MAP_SCALE_FACTOR = default 

762MEASURE_UNIT = inch 

763#-------- Calendar/Time Parameters ---------- 

764TIME_FORMAT_PRIMARY = full 

765TIME_FORMAT_SECONDARY = full 

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

767TIME_IS_INTERVAL = OFF 

768TIME_INTERVAL_FRACTION = 0.5 

769TIME_LANGUAGE = us 

770TIME_UNIT = d 

771TIME_WEEK_START = Sunday 

772Y2K_OFFSET_YEAR = 1950 

773#-------- Miscellaneous Parameters ---------- 

774HISTORY = TRUE 

775INTERPOLANT = akima 

776LINE_STEP = 0.01i 

777VECTOR_SHAPE = 0 

778VERBOSE = FALSE 

779''' 

780 

781_gmt_defaults_by_version['4.5.2'] = r''' 

782# 

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

784# 

785#-------- Plot Media Parameters ------------- 

786PAGE_COLOR = white 

787PAGE_ORIENTATION = portrait 

788PAPER_MEDIA = a4+ 

789#-------- Basemap Annotation Parameters ------ 

790ANNOT_MIN_ANGLE = 20 

791ANNOT_MIN_SPACING = 0 

792ANNOT_FONT_PRIMARY = Helvetica 

793ANNOT_FONT_SIZE_PRIMARY = 14p 

794ANNOT_OFFSET_PRIMARY = 0.075i 

795ANNOT_FONT_SECONDARY = Helvetica 

796ANNOT_FONT_SIZE_SECONDARY = 16p 

797ANNOT_OFFSET_SECONDARY = 0.075i 

798DEGREE_SYMBOL = ring 

799HEADER_FONT = Helvetica 

800HEADER_FONT_SIZE = 36p 

801HEADER_OFFSET = 0.1875i 

802LABEL_FONT = Helvetica 

803LABEL_FONT_SIZE = 14p 

804LABEL_OFFSET = 0.1125i 

805OBLIQUE_ANNOTATION = 1 

806PLOT_CLOCK_FORMAT = hh:mm:ss 

807PLOT_DATE_FORMAT = yyyy-mm-dd 

808PLOT_DEGREE_FORMAT = +ddd:mm:ss 

809Y_AXIS_TYPE = hor_text 

810#-------- Basemap Layout Parameters --------- 

811BASEMAP_AXES = WESN 

812BASEMAP_FRAME_RGB = black 

813BASEMAP_TYPE = plain 

814FRAME_PEN = 1.25p 

815FRAME_WIDTH = 0.075i 

816GRID_CROSS_SIZE_PRIMARY = 0i 

817GRID_PEN_PRIMARY = 0.25p 

818GRID_CROSS_SIZE_SECONDARY = 0i 

819GRID_PEN_SECONDARY = 0.5p 

820MAP_SCALE_HEIGHT = 0.075i 

821POLAR_CAP = 85/90 

822TICK_LENGTH = 0.075i 

823TICK_PEN = 0.5p 

824X_AXIS_LENGTH = 9i 

825Y_AXIS_LENGTH = 6i 

826X_ORIGIN = 1i 

827Y_ORIGIN = 1i 

828UNIX_TIME = FALSE 

829UNIX_TIME_POS = BL/-0.75i/-0.75i 

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

831#-------- Color System Parameters ----------- 

832COLOR_BACKGROUND = black 

833COLOR_FOREGROUND = white 

834COLOR_NAN = 128 

835COLOR_IMAGE = adobe 

836COLOR_MODEL = rgb 

837HSV_MIN_SATURATION = 1 

838HSV_MAX_SATURATION = 0.1 

839HSV_MIN_VALUE = 0.3 

840HSV_MAX_VALUE = 1 

841#-------- PostScript Parameters ------------- 

842CHAR_ENCODING = ISOLatin1+ 

843DOTS_PR_INCH = 300 

844GLOBAL_X_SCALE = 1 

845GLOBAL_Y_SCALE = 1 

846N_COPIES = 1 

847PS_COLOR = rgb 

848PS_IMAGE_COMPRESS = lzw 

849PS_IMAGE_FORMAT = ascii 

850PS_LINE_CAP = round 

851PS_LINE_JOIN = miter 

852PS_MITER_LIMIT = 35 

853PS_VERBOSE = FALSE 

854TRANSPARENCY = 0 

855#-------- I/O Format Parameters ------------- 

856D_FORMAT = %.12lg 

857FIELD_DELIMITER = tab 

858GRIDFILE_FORMAT = nf 

859GRIDFILE_SHORTHAND = FALSE 

860INPUT_CLOCK_FORMAT = hh:mm:ss 

861INPUT_DATE_FORMAT = yyyy-mm-dd 

862IO_HEADER = FALSE 

863N_HEADER_RECS = 1 

864NAN_RECORDS = pass 

865OUTPUT_CLOCK_FORMAT = hh:mm:ss 

866OUTPUT_DATE_FORMAT = yyyy-mm-dd 

867OUTPUT_DEGREE_FORMAT = D 

868XY_TOGGLE = FALSE 

869#-------- Projection Parameters ------------- 

870ELLIPSOID = WGS-84 

871MAP_SCALE_FACTOR = default 

872MEASURE_UNIT = inch 

873#-------- Calendar/Time Parameters ---------- 

874TIME_FORMAT_PRIMARY = full 

875TIME_FORMAT_SECONDARY = full 

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

877TIME_IS_INTERVAL = OFF 

878TIME_INTERVAL_FRACTION = 0.5 

879TIME_LANGUAGE = us 

880TIME_UNIT = d 

881TIME_WEEK_START = Sunday 

882Y2K_OFFSET_YEAR = 1950 

883#-------- Miscellaneous Parameters ---------- 

884HISTORY = TRUE 

885INTERPOLANT = akima 

886LINE_STEP = 0.01i 

887VECTOR_SHAPE = 0 

888VERBOSE = FALSE 

889''' 

890 

891_gmt_defaults_by_version['4.5.3'] = r''' 

892# 

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

894# 

895#-------- Plot Media Parameters ------------- 

896PAGE_COLOR = white 

897PAGE_ORIENTATION = portrait 

898PAPER_MEDIA = a4+ 

899#-------- Basemap Annotation Parameters ------ 

900ANNOT_MIN_ANGLE = 20 

901ANNOT_MIN_SPACING = 0 

902ANNOT_FONT_PRIMARY = Helvetica 

903ANNOT_FONT_SIZE_PRIMARY = 14p 

904ANNOT_OFFSET_PRIMARY = 0.075i 

905ANNOT_FONT_SECONDARY = Helvetica 

906ANNOT_FONT_SIZE_SECONDARY = 16p 

907ANNOT_OFFSET_SECONDARY = 0.075i 

908DEGREE_SYMBOL = ring 

909HEADER_FONT = Helvetica 

910HEADER_FONT_SIZE = 36p 

911HEADER_OFFSET = 0.1875i 

912LABEL_FONT = Helvetica 

913LABEL_FONT_SIZE = 14p 

914LABEL_OFFSET = 0.1125i 

915OBLIQUE_ANNOTATION = 1 

916PLOT_CLOCK_FORMAT = hh:mm:ss 

917PLOT_DATE_FORMAT = yyyy-mm-dd 

918PLOT_DEGREE_FORMAT = +ddd:mm:ss 

919Y_AXIS_TYPE = hor_text 

920#-------- Basemap Layout Parameters --------- 

921BASEMAP_AXES = WESN 

922BASEMAP_FRAME_RGB = black 

923BASEMAP_TYPE = plain 

924FRAME_PEN = 1.25p 

925FRAME_WIDTH = 0.075i 

926GRID_CROSS_SIZE_PRIMARY = 0i 

927GRID_PEN_PRIMARY = 0.25p 

928GRID_CROSS_SIZE_SECONDARY = 0i 

929GRID_PEN_SECONDARY = 0.5p 

930MAP_SCALE_HEIGHT = 0.075i 

931POLAR_CAP = 85/90 

932TICK_LENGTH = 0.075i 

933TICK_PEN = 0.5p 

934X_AXIS_LENGTH = 9i 

935Y_AXIS_LENGTH = 6i 

936X_ORIGIN = 1i 

937Y_ORIGIN = 1i 

938UNIX_TIME = FALSE 

939UNIX_TIME_POS = BL/-0.75i/-0.75i 

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

941#-------- Color System Parameters ----------- 

942COLOR_BACKGROUND = black 

943COLOR_FOREGROUND = white 

944COLOR_NAN = 128 

945COLOR_IMAGE = adobe 

946COLOR_MODEL = rgb 

947HSV_MIN_SATURATION = 1 

948HSV_MAX_SATURATION = 0.1 

949HSV_MIN_VALUE = 0.3 

950HSV_MAX_VALUE = 1 

951#-------- PostScript Parameters ------------- 

952CHAR_ENCODING = ISOLatin1+ 

953DOTS_PR_INCH = 300 

954GLOBAL_X_SCALE = 1 

955GLOBAL_Y_SCALE = 1 

956N_COPIES = 1 

957PS_COLOR = rgb 

958PS_IMAGE_COMPRESS = lzw 

959PS_IMAGE_FORMAT = ascii 

960PS_LINE_CAP = round 

961PS_LINE_JOIN = miter 

962PS_MITER_LIMIT = 35 

963PS_VERBOSE = FALSE 

964TRANSPARENCY = 0 

965#-------- I/O Format Parameters ------------- 

966D_FORMAT = %.12lg 

967FIELD_DELIMITER = tab 

968GRIDFILE_FORMAT = nf 

969GRIDFILE_SHORTHAND = FALSE 

970INPUT_CLOCK_FORMAT = hh:mm:ss 

971INPUT_DATE_FORMAT = yyyy-mm-dd 

972IO_HEADER = FALSE 

973N_HEADER_RECS = 1 

974NAN_RECORDS = pass 

975OUTPUT_CLOCK_FORMAT = hh:mm:ss 

976OUTPUT_DATE_FORMAT = yyyy-mm-dd 

977OUTPUT_DEGREE_FORMAT = D 

978XY_TOGGLE = FALSE 

979#-------- Projection Parameters ------------- 

980ELLIPSOID = WGS-84 

981MAP_SCALE_FACTOR = default 

982MEASURE_UNIT = inch 

983#-------- Calendar/Time Parameters ---------- 

984TIME_FORMAT_PRIMARY = full 

985TIME_FORMAT_SECONDARY = full 

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

987TIME_IS_INTERVAL = OFF 

988TIME_INTERVAL_FRACTION = 0.5 

989TIME_LANGUAGE = us 

990TIME_UNIT = d 

991TIME_WEEK_START = Sunday 

992Y2K_OFFSET_YEAR = 1950 

993#-------- Miscellaneous Parameters ---------- 

994HISTORY = TRUE 

995INTERPOLANT = akima 

996LINE_STEP = 0.01i 

997VECTOR_SHAPE = 0 

998VERBOSE = FALSE 

999''' 

1000 

1001_gmt_defaults_by_version['5.1.2'] = r''' 

1002# 

1003# GMT 5.1.2 Defaults file 

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

1005# $Revision: 13836 $ 

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

1007# 

1008# COLOR Parameters 

1009# 

1010COLOR_BACKGROUND = black 

1011COLOR_FOREGROUND = white 

1012COLOR_NAN = 127.5 

1013COLOR_MODEL = none 

1014COLOR_HSV_MIN_S = 1 

1015COLOR_HSV_MAX_S = 0.1 

1016COLOR_HSV_MIN_V = 0.3 

1017COLOR_HSV_MAX_V = 1 

1018# 

1019# DIR Parameters 

1020# 

1021DIR_DATA = 

1022DIR_DCW = 

1023DIR_GSHHG = 

1024# 

1025# FONT Parameters 

1026# 

1027FONT_ANNOT_PRIMARY = 14p,Helvetica,black 

1028FONT_ANNOT_SECONDARY = 16p,Helvetica,black 

1029FONT_LABEL = 14p,Helvetica,black 

1030FONT_LOGO = 8p,Helvetica,black 

1031FONT_TITLE = 24p,Helvetica,black 

1032# 

1033# FORMAT Parameters 

1034# 

1035FORMAT_CLOCK_IN = hh:mm:ss 

1036FORMAT_CLOCK_OUT = hh:mm:ss 

1037FORMAT_CLOCK_MAP = hh:mm:ss 

1038FORMAT_DATE_IN = yyyy-mm-dd 

1039FORMAT_DATE_OUT = yyyy-mm-dd 

1040FORMAT_DATE_MAP = yyyy-mm-dd 

1041FORMAT_GEO_OUT = D 

1042FORMAT_GEO_MAP = ddd:mm:ss 

1043FORMAT_FLOAT_OUT = %.12g 

1044FORMAT_FLOAT_MAP = %.12g 

1045FORMAT_TIME_PRIMARY_MAP = full 

1046FORMAT_TIME_SECONDARY_MAP = full 

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

1048# 

1049# GMT Miscellaneous Parameters 

1050# 

1051GMT_COMPATIBILITY = 4 

1052GMT_CUSTOM_LIBS = 

1053GMT_EXTRAPOLATE_VAL = NaN 

1054GMT_FFT = auto 

1055GMT_HISTORY = true 

1056GMT_INTERPOLANT = akima 

1057GMT_TRIANGULATE = Shewchuk 

1058GMT_VERBOSE = compat 

1059GMT_LANGUAGE = us 

1060# 

1061# I/O Parameters 

1062# 

1063IO_COL_SEPARATOR = tab 

1064IO_GRIDFILE_FORMAT = nf 

1065IO_GRIDFILE_SHORTHAND = false 

1066IO_HEADER = false 

1067IO_N_HEADER_RECS = 0 

1068IO_NAN_RECORDS = pass 

1069IO_NC4_CHUNK_SIZE = auto 

1070IO_NC4_DEFLATION_LEVEL = 3 

1071IO_LONLAT_TOGGLE = false 

1072IO_SEGMENT_MARKER = > 

1073# 

1074# MAP Parameters 

1075# 

1076MAP_ANNOT_MIN_ANGLE = 20 

1077MAP_ANNOT_MIN_SPACING = 0p 

1078MAP_ANNOT_OBLIQUE = 1 

1079MAP_ANNOT_OFFSET_PRIMARY = 0.075i 

1080MAP_ANNOT_OFFSET_SECONDARY = 0.075i 

1081MAP_ANNOT_ORTHO = we 

1082MAP_DEFAULT_PEN = default,black 

1083MAP_DEGREE_SYMBOL = ring 

1084MAP_FRAME_AXES = WESNZ 

1085MAP_FRAME_PEN = thicker,black 

1086MAP_FRAME_TYPE = fancy 

1087MAP_FRAME_WIDTH = 5p 

1088MAP_GRID_CROSS_SIZE_PRIMARY = 0p 

1089MAP_GRID_CROSS_SIZE_SECONDARY = 0p 

1090MAP_GRID_PEN_PRIMARY = default,black 

1091MAP_GRID_PEN_SECONDARY = thinner,black 

1092MAP_LABEL_OFFSET = 0.1944i 

1093MAP_LINE_STEP = 0.75p 

1094MAP_LOGO = false 

1095MAP_LOGO_POS = BL/-54p/-54p 

1096MAP_ORIGIN_X = 1i 

1097MAP_ORIGIN_Y = 1i 

1098MAP_POLAR_CAP = 85/90 

1099MAP_SCALE_HEIGHT = 5p 

1100MAP_TICK_LENGTH_PRIMARY = 5p/2.5p 

1101MAP_TICK_LENGTH_SECONDARY = 15p/3.75p 

1102MAP_TICK_PEN_PRIMARY = thinner,black 

1103MAP_TICK_PEN_SECONDARY = thinner,black 

1104MAP_TITLE_OFFSET = 14p 

1105MAP_VECTOR_SHAPE = 0 

1106# 

1107# Projection Parameters 

1108# 

1109PROJ_AUX_LATITUDE = authalic 

1110PROJ_ELLIPSOID = WGS-84 

1111PROJ_LENGTH_UNIT = cm 

1112PROJ_MEAN_RADIUS = authalic 

1113PROJ_SCALE_FACTOR = default 

1114# 

1115# PostScript Parameters 

1116# 

1117PS_CHAR_ENCODING = ISOLatin1+ 

1118PS_COLOR_MODEL = rgb 

1119PS_COMMENTS = false 

1120PS_IMAGE_COMPRESS = deflate,5 

1121PS_LINE_CAP = butt 

1122PS_LINE_JOIN = miter 

1123PS_MITER_LIMIT = 35 

1124PS_MEDIA = a4 

1125PS_PAGE_COLOR = white 

1126PS_PAGE_ORIENTATION = portrait 

1127PS_SCALE_X = 1 

1128PS_SCALE_Y = 1 

1129PS_TRANSPARENCY = Normal 

1130# 

1131# Calendar/Time Parameters 

1132# 

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

1134TIME_IS_INTERVAL = off 

1135TIME_INTERVAL_FRACTION = 0.5 

1136TIME_UNIT = s 

1137TIME_WEEK_START = Monday 

1138TIME_Y2K_OFFSET_YEAR = 1950 

1139''' 

1140 

1141 

1142def get_gmt_version(gmtdefaultsbinary, gmthomedir=None): 

1143 args = [gmtdefaultsbinary] 

1144 

1145 environ = os.environ.copy() 

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

1147 

1148 p = subprocess.Popen( 

1149 args, 

1150 stdout=subprocess.PIPE, 

1151 stderr=subprocess.PIPE, 

1152 env=environ) 

1153 

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

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

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

1157 

1158 if not m: 

1159 raise GMTInstallationProblem( 

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

1161 % gmtdefaultsbinary) 

1162 

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

1164 

1165 

1166def detect_gmt_installations(): 

1167 

1168 installations = {} 

1169 errmesses = [] 

1170 

1171 # GMT 4.x: 

1172 try: 

1173 p = subprocess.Popen( 

1174 ['GMT'], 

1175 stdout=subprocess.PIPE, 

1176 stderr=subprocess.PIPE) 

1177 

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

1179 

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

1181 if not m: 

1182 raise GMTInstallationProblem( 

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

1184 

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

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

1187 

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

1189 if not m: 

1190 raise GMTInstallationProblem( 

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

1192 

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

1194 

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

1196 if not m: 

1197 raise GMTInstallationProblem( 

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

1199 

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

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

1202 raise GMTInstallationProblem( 

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

1204 

1205 gmthome = gmtshare[:-6] 

1206 

1207 installations[version] = { 

1208 'home': gmthome, 

1209 'bin': gmtbin} 

1210 

1211 except OSError as e: 

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

1213 

1214 try: 

1215 version = str(subprocess.check_output( 

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

1217 gmtbin = str(subprocess.check_output( 

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

1219 installations[version] = { 

1220 'bin': gmtbin} 

1221 

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

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

1224 

1225 if not installations: 

1226 s = [] 

1227 for (progname, errmess) in errmesses: 

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

1229 

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

1231 

1232 return installations 

1233 

1234 

1235def appropriate_defaults_version(version): 

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

1237 for iavail, avail in enumerate(avails): 

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

1239 return version 

1240 

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

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

1243 

1244 return avails[-1] 

1245 

1246 

1247def gmt_default_config(version): 

1248 ''' 

1249 Get default GMT configuration dict for given version. 

1250 ''' 

1251 

1252 xversion = appropriate_defaults_version(version) 

1253 

1254 # if not version in _gmt_defaults_by_version: 

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

1256 

1257 gmt_defaults = _gmt_defaults_by_version[xversion] 

1258 

1259 d = {} 

1260 for line in gmt_defaults.splitlines(): 

1261 sline = line.strip() 

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

1263 continue 

1264 

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

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

1267 

1268 return d 

1269 

1270 

1271def diff_defaults(v1, v2): 

1272 d1 = gmt_default_config(v1) 

1273 d2 = gmt_default_config(v2) 

1274 for k in d1: 

1275 if k not in d2: 

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

1277 else: 

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

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

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

1281 

1282 for k in d2: 

1283 if k not in d1: 

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

1285 

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

1287 

1288 

1289def check_gmt_installation(installation): 

1290 

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

1292 bin_dir = installation['bin'] 

1293 version = installation['version'] 

1294 

1295 for d in home_dir, bin_dir: 

1296 if d is not None: 

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

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

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

1300 

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

1302 

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

1304 gmtdefaults = pjoin(bin_dir, 'gmtdefaults') 

1305 

1306 versionfound = get_gmt_version(gmtdefaults, home_dir) 

1307 

1308 if versionfound != version: 

1309 raise GMTInstallationProblem(( 

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

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

1312 version, versionfound, gmtdefaults)) 

1313 

1314 

1315def get_gmt_installation(version): 

1316 setup_gmt_installations() 

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

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

1319 % (version, newest_installed_gmt_version())) 

1320 

1321 version = 'newest' 

1322 

1323 if version == 'newest': 

1324 version = newest_installed_gmt_version() 

1325 

1326 installation = dict(_gmt_installations[version]) 

1327 

1328 return installation 

1329 

1330 

1331def setup_gmt_installations(): 

1332 if not setup_gmt_installations.have_done: 

1333 if not _gmt_installations: 

1334 

1335 _gmt_installations.update(detect_gmt_installations()) 

1336 

1337 # store defaults as dicts into the gmt installations dicts 

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

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

1340 installation['version'] = version 

1341 

1342 for installation in _gmt_installations.values(): 

1343 check_gmt_installation(installation) 

1344 

1345 setup_gmt_installations.have_done = True 

1346 

1347 

1348setup_gmt_installations.have_done = False 

1349 

1350_paper_sizes_a = '''A0 2380 3368 

1351 A1 1684 2380 

1352 A2 1190 1684 

1353 A3 842 1190 

1354 A4 595 842 

1355 A5 421 595 

1356 A6 297 421 

1357 A7 210 297 

1358 A8 148 210 

1359 A9 105 148 

1360 A10 74 105 

1361 B0 2836 4008 

1362 B1 2004 2836 

1363 B2 1418 2004 

1364 B3 1002 1418 

1365 B4 709 1002 

1366 B5 501 709 

1367 archA 648 864 

1368 archB 864 1296 

1369 archC 1296 1728 

1370 archD 1728 2592 

1371 archE 2592 3456 

1372 flsa 612 936 

1373 halfletter 396 612 

1374 note 540 720 

1375 letter 612 792 

1376 legal 612 1008 

1377 11x17 792 1224 

1378 ledger 1224 792''' 

1379 

1380 

1381_paper_sizes = {} 

1382 

1383 

1384def setup_paper_sizes(): 

1385 if not _paper_sizes: 

1386 for line in _paper_sizes_a.splitlines(): 

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

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

1389 

1390 

1391def get_paper_size(k): 

1392 setup_paper_sizes() 

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

1394 

1395 

1396def all_paper_sizes(): 

1397 setup_paper_sizes() 

1398 return _paper_sizes 

1399 

1400 

1401def measure_unit(gmt_config): 

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

1403 if k in gmt_config: 

1404 return gmt_config[k] 

1405 

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

1407 

1408 

1409def paper_media(gmt_config): 

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

1411 if k in gmt_config: 

1412 return gmt_config[k] 

1413 

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

1415 

1416 

1417def page_orientation(gmt_config): 

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

1419 if k in gmt_config: 

1420 return gmt_config[k] 

1421 

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

1423 

1424 

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

1426 

1427 leftmargin, topmargin, rightmargin, bottommargin = margins 

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

1429 

1430 paper_size = get_paper_size(paper_media(gmt_config)) 

1431 if not portrait: 

1432 paper_size = paper_size[1], paper_size[0] 

1433 

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

1435 2.0 + leftmargin 

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

1437 2.0 + bottommargin 

1438 

1439 if portrait: 

1440 bb1 = int((xoffset - leftmargin)) 

1441 bb2 = int((yoffset - bottommargin)) 

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

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

1444 else: 

1445 bb1 = int((yoffset - topmargin)) 

1446 bb2 = int((xoffset - leftmargin)) 

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

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

1449 

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

1451 

1452 

1453def gmtdefaults_as_text(version='newest'): 

1454 

1455 ''' 

1456 Get the built-in gmtdefaults. 

1457 ''' 

1458 

1459 if version not in _gmt_installations: 

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

1461 % (version, newest_installed_gmt_version())) 

1462 version = 'newest' 

1463 

1464 if version == 'newest': 

1465 version = newest_installed_gmt_version() 

1466 

1467 return _gmt_defaults_by_version[version] 

1468 

1469 

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

1471 ''' 

1472 Write COARDS compliant netcdf (grd) file. 

1473 ''' 

1474 

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

1476 ny, nx = z.shape 

1477 nc = netcdf.netcdf_file(filename, 'w') 

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

1479 

1480 if naming == 'xy': 

1481 kx, ky = 'x', 'y' 

1482 else: 

1483 kx, ky = 'lon', 'lat' 

1484 

1485 nc.node_offset = 0 

1486 if title is not None: 

1487 nc.title = title 

1488 

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

1490 nc.createDimension(kx, nx) 

1491 nc.createDimension(ky, ny) 

1492 

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

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

1495 if naming == 'xy': 

1496 xvar.long_name = kx 

1497 yvar.long_name = ky 

1498 else: 

1499 xvar.long_name = 'longitude' 

1500 xvar.units = 'degrees_east' 

1501 yvar.long_name = 'latitude' 

1502 yvar.units = 'degrees_north' 

1503 

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

1505 

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

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

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

1509 

1510 nc.close() 

1511 

1512 

1513def to_array(var): 

1514 arr = var[:].copy() 

1515 if hasattr(var, 'scale_factor'): 

1516 arr *= var.scale_factor 

1517 

1518 if hasattr(var, 'add_offset'): 

1519 arr += var.add_offset 

1520 

1521 return arr 

1522 

1523 

1524def loadgrd(filename): 

1525 ''' 

1526 Read COARDS compliant netcdf (grd) file. 

1527 ''' 

1528 

1529 nc = netcdf.netcdf_file(filename, 'r') 

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

1531 kx = 'x' 

1532 ky = 'y' 

1533 if 'lon' in vkeys: 

1534 kx = 'lon' 

1535 if 'lat' in vkeys: 

1536 ky = 'lat' 

1537 

1538 kz = 'z' 

1539 if 'altitude' in vkeys: 

1540 kz = 'altitude' 

1541 

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

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

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

1545 

1546 nc.close() 

1547 return x, y, z 

1548 

1549 

1550def centers_to_edges(asorted): 

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

1552 

1553 

1554def nvals(asorted): 

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

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

1557 

1558 

1559def guess_vals(asorted): 

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

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

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

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

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

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

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

1567 

1568 

1569def blockmean(asorted, b): 

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

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

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

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

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

1575 return ( 

1576 asorted[indis[:-1]], 

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

1578 

1579 

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

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

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

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

1584 

1585 zindi = yindi*nx+xindi 

1586 order = num.argsort(zindi) 

1587 z = z[order] 

1588 zindi = zindi[order] 

1589 

1590 zindi, z = blockmean(zindi, z) 

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

1592 znew[:] = num.nan 

1593 znew[zindi] = z 

1594 return znew.reshape(ny, nx) 

1595 

1596 

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

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

1599 xs = x_sorted 

1600 ys = y_sorted 

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

1602 if mode == 'nonrandom': 

1603 return nxs, nys, 0 

1604 elif xs.size == nxs*nys: 

1605 # exact match 

1606 return nxs, nys, 0 

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

1608 # possibly randomly sampled 

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

1610 nys = nxs 

1611 return nxs, nys, 2 

1612 else: 

1613 return nxs, nys, 1 

1614 

1615 

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

1617 ''' 

1618 Grid tabular XYZ data by binning. 

1619 

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

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

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

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

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

1625 

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

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

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

1629 ''' 

1630 

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

1632 assert x.size == y.size == z.size 

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

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

1635 if badness <= 1: 

1636 xf = guess_vals(xs) 

1637 yf = guess_vals(ys) 

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

1639 else: 

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

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

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

1643 

1644 return xf, yf, zf 

1645 

1646 

1647def tabledata(xf, yf, zf): 

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

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

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

1651 z = zf.flatten() 

1652 return x, y, z 

1653 

1654 

1655def double1d(a): 

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

1657 a2[::2] = a 

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

1659 return a2 

1660 

1661 

1662def double2d(f): 

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

1664 f2[:, :] = num.nan 

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

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

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

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

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

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

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

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

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

1674 return f2 

1675 

1676 

1677def doublegrid(x, y, z): 

1678 x2 = double1d(x) 

1679 y2 = double1d(y) 

1680 z2 = double2d(z) 

1681 return x2, y2, z2 

1682 

1683 

1684class Guru(object): 

1685 ''' 

1686 Abstract base class providing template interpolation, accessible as 

1687 attributes. 

1688 

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

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

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

1692 with the templates. 

1693 ''' 

1694 

1695 def __init__(self): 

1696 self.templates = {} 

1697 

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

1699 params = self.get_params(**kwargs) 

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

1701 return strings 

1702 

1703 # hand through templates dict 

1704 def __getitem__(self, template_name): 

1705 return self.templates[template_name] 

1706 

1707 def __setitem__(self, template_name, template): 

1708 self.templates[template_name] = template 

1709 

1710 def __contains__(self, template_name): 

1711 return template_name in self.templates 

1712 

1713 def __iter__(self): 

1714 return iter(self.templates) 

1715 

1716 def __len__(self): 

1717 return len(self.templates) 

1718 

1719 def __delitem__(self, template_name): 

1720 del(self.templates[template_name]) 

1721 

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

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

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

1725 

1726 def __getattr__(self, template_names): 

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

1728 raise AttributeError(template_names) 

1729 

1730 def f(**kwargs): 

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

1732 

1733 return f 

1734 

1735 

1736class Ax(AutoScaler): 

1737 ''' 

1738 Ax description with autoscaling capabilities. 

1739 

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

1741 public attributes, plus the following additional attributes 

1742 (with default values given in paranthesis): 

1743 

1744 .. py:attribute:: label 

1745 

1746 Ax label (without unit). 

1747 

1748 .. py:attribute:: unit 

1749 

1750 Physical unit of the data attached to this ax. 

1751 

1752 .. py:attribute:: scaled_unit 

1753 

1754 (see below) 

1755 

1756 .. py:attribute:: scaled_unit_factor 

1757 

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

1759 

1760 unit = scaled_unit_factor x scaled_unit. 

1761 

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

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

1764 1e9.) 

1765 

1766 .. py:attribute:: limits 

1767 

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

1769 

1770 .. py:attribute:: masking 

1771 

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

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

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

1775 

1776 ''' 

1777 

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

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

1780 

1781 AutoScaler.__init__(self, **kwargs) 

1782 self.label = label 

1783 self.unit = unit 

1784 self.scaled_unit_factor = scaled_unit_factor 

1785 self.scaled_unit = scaled_unit 

1786 self.limits = limits 

1787 self.masking = masking 

1788 

1789 def label_str(self, exp, unit): 

1790 ''' 

1791 Get label string including the unit and multiplier. 

1792 ''' 

1793 

1794 slabel, sunit, sexp = '', '', '' 

1795 if self.label: 

1796 slabel = self.label 

1797 

1798 if unit or exp != 0: 

1799 if exp != 0: 

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

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

1802 else: 

1803 sunit = '[ %s ]' % unit 

1804 

1805 p = [] 

1806 if slabel: 

1807 p.append(slabel) 

1808 

1809 if sunit: 

1810 p.append(sunit) 

1811 

1812 return ' '.join(p) 

1813 

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

1815 override_scaled_unit_factor=None): 

1816 

1817 ''' 

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

1819 

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

1821 multiplier for given data range. 

1822 

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

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

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

1826 scaling applied. 

1827 ''' 

1828 

1829 sf = self.scaled_unit_factor 

1830 

1831 if override_scaled_unit_factor is not None: 

1832 sf = override_scaled_unit_factor 

1833 

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

1835 

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

1837 if self.inc is not None: 

1838 inc = self.inc*sf 

1839 

1840 if ax_projection: 

1841 exp = self.make_exp(inc) 

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

1843 unit = self.unit 

1844 else: 

1845 unit = self.scaled_unit 

1846 label = self.label_str(exp, unit) 

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

1848 else: 

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

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

1851 

1852 

1853class ScaleGuru(Guru): 

1854 

1855 ''' 

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

1857 

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

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

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

1861 arguments, which are required for most GMT commands. 

1862 

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

1864 :py:class:`AutoScaler` classes at the level, where it can not be handled 

1865 anymore by looking at a single dimension of the dataset's data, e.g.: 

1866 

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

1868 

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

1870 limits imposed on other axes. 

1871 

1872 ''' 

1873 

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

1875 percent_interval=None, copy_from=None): 

1876 

1877 Guru.__init__(self) 

1878 

1879 if copy_from: 

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

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

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

1883 self.aspect = copy_from.aspect 

1884 

1885 if percent_interval is not None: 

1886 from scipy.stats import scoreatpercentile as scap 

1887 

1888 self.templates = dict( 

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

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

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

1892 

1893 maxdim = 2 

1894 if data_tuples: 

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

1896 else: 

1897 if axes: 

1898 maxdim = len(axes) 

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

1900 if axes is not None: 

1901 self.axes = axes 

1902 else: 

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

1904 

1905 # sophisticated data-range calculation 

1906 data_ranges = [None] * maxdim 

1907 for dt_ in data_tuples: 

1908 dt = num.asarray(dt_) 

1909 in_range = True 

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

1911 if ax.limits and ax.masking: 

1912 ax_limits = list(ax.limits) 

1913 if ax_limits[0] is None: 

1914 ax_limits[0] = -num.inf 

1915 if ax_limits[1] is None: 

1916 ax_limits[1] = num.inf 

1917 in_range = num.logical_and( 

1918 in_range, 

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

1920 

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

1922 

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

1924 if len(x) >= 1: 

1925 if in_range is not True: 

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

1927 if percent_interval is None: 

1928 range_this = ( 

1929 num.nanmin(xmasked), 

1930 num.nanmax(xmasked)) 

1931 else: 

1932 xmasked_finite = num.compress( 

1933 num.isfinite(xmasked), xmasked) 

1934 range_this = ( 

1935 scap(xmasked_finite, 

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

1937 scap(xmasked_finite, 

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

1939 else: 

1940 if percent_interval is None: 

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

1942 else: 

1943 xmasked_finite = num.compress( 

1944 num.isfinite(xmasked), xmasked) 

1945 range_this = ( 

1946 scap(xmasked_finite, 

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

1948 scap(xmasked_finite, 

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

1950 else: 

1951 range_this = (0., 1.) 

1952 

1953 if ax.limits: 

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

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

1956 range_this[1]) 

1957 

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

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

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

1961 

1962 else: 

1963 range_this = ax.limits 

1964 

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

1966 data_ranges[i] = range_this 

1967 else: 

1968 mi, ma = range_this 

1969 if data_ranges[i] is not None: 

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

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

1972 

1973 data_ranges[i] = (mi, ma) 

1974 

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

1976 if data_ranges[i] is None or not ( 

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

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

1979 

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

1981 

1982 self.data_ranges = data_ranges 

1983 self.aspect = aspect 

1984 

1985 def copy(self): 

1986 return ScaleGuru(copy_from=self) 

1987 

1988 def get_params(self, ax_projection=False): 

1989 

1990 ''' 

1991 Get dict with output parameters. 

1992 

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

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

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

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

1997 

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

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

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

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

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

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

2004 label string. 

2005 ''' 

2006 

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

2008 self.data_ranges[0], ax_projection) 

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

2010 self.data_ranges[1], ax_projection) 

2011 if len(self.axes) > 2: 

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

2013 self.data_ranges[2], ax_projection) 

2014 

2015 # enforce certain aspect, if needed 

2016 if self.aspect is not None: 

2017 xwid = xma-xmi 

2018 ywid = yma-ymi 

2019 if ywid < xwid*self.aspect: 

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

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

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

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

2024 override_scaled_unit_factor=1.) 

2025 

2026 elif xwid < ywid/self.aspect: 

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

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

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

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

2031 override_scaled_unit_factor=1.) 

2032 

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

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

2035 if len(self.axes) > 2: 

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

2037 

2038 return params 

2039 

2040 

2041class GumSpring(object): 

2042 

2043 ''' 

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

2045 ''' 

2046 

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

2048 self.minimal = minimal 

2049 if grow is None: 

2050 if minimal is None: 

2051 self.grow = 1.0 

2052 else: 

2053 self.grow = 0.0 

2054 else: 

2055 self.grow = grow 

2056 self.value = 1.0 

2057 

2058 def get_minimal(self): 

2059 if self.minimal is not None: 

2060 return self.minimal 

2061 else: 

2062 return 0.0 

2063 

2064 def get_grow(self): 

2065 return self.grow 

2066 

2067 def set_value(self, value): 

2068 self.value = value 

2069 

2070 def get_value(self): 

2071 return self.value 

2072 

2073 

2074def distribute(sizes, grows, space): 

2075 sizes = list(sizes) 

2076 gsum = sum(grows) 

2077 if gsum > 0.0: 

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

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

2080 return sizes 

2081 

2082 

2083class Widget(Guru): 

2084 

2085 ''' 

2086 Base class of the gmtpy layout system. 

2087 

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

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

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

2091 

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

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

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

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

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

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

2098 

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

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

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

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

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

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

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

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

2107 e.g. for use in the :py:meth:`save` method of :py:class:`GMT` instances. 

2108 

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

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

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

2112 ''' 

2113 

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

2115 

2116 ''' 

2117 Create new widget. 

2118 ''' 

2119 

2120 Guru.__init__(self) 

2121 

2122 self.templates = dict( 

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

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

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

2126 

2127 if horizontal is None: 

2128 self.horizontal = GumSpring() 

2129 else: 

2130 self.horizontal = horizontal 

2131 

2132 if vertical is None: 

2133 self.vertical = GumSpring() 

2134 else: 

2135 self.vertical = vertical 

2136 

2137 self.aspect = None 

2138 self.parent = parent 

2139 self.dirty = True 

2140 

2141 def set_parent(self, parent): 

2142 

2143 ''' 

2144 Set the parent widget. 

2145 

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

2147 methods are responsible for calling this. 

2148 ''' 

2149 

2150 self.parent = parent 

2151 self.dirtyfy() 

2152 

2153 def get_parent(self): 

2154 

2155 ''' 

2156 Get the widgets parent widget. 

2157 ''' 

2158 

2159 return self.parent 

2160 

2161 def get_root(self): 

2162 

2163 ''' 

2164 Get the root widget in the layout hierarchy. 

2165 ''' 

2166 

2167 if self.parent is not None: 

2168 return self.get_parent() 

2169 else: 

2170 return self 

2171 

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

2173 

2174 ''' 

2175 Set the horizontal sizing policy of the Widget. 

2176 

2177 

2178 :param minimal: new minimal width of the widget 

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

2180 ''' 

2181 

2182 self.horizontal = GumSpring(minimal, grow) 

2183 self.dirtyfy() 

2184 

2185 def get_horizontal(self): 

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

2187 

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

2189 

2190 ''' 

2191 Set the horizontal sizing policy of the Widget. 

2192 

2193 :param minimal: new minimal height of the widget 

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

2195 ''' 

2196 

2197 self.vertical = GumSpring(minimal, grow) 

2198 self.dirtyfy() 

2199 

2200 def get_vertical(self): 

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

2202 

2203 def set_aspect(self, aspect=None): 

2204 

2205 ''' 

2206 Set aspect constraint on the widget. 

2207 

2208 The aspect is given as height divided by width. 

2209 ''' 

2210 

2211 self.aspect = aspect 

2212 self.dirtyfy() 

2213 

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

2215 

2216 ''' 

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

2218 call. 

2219 ''' 

2220 

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

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

2223 self.set_aspect(aspect) 

2224 

2225 def get_policy(self): 

2226 mh, gh = self.get_horizontal() 

2227 mv, gv = self.get_vertical() 

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

2229 

2230 def legalize(self, size, offset): 

2231 

2232 ''' 

2233 Get legal size for widget. 

2234 

2235 Returns: (new_size, new_offset) 

2236 

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

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

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

2240 ''' 

2241 

2242 sh, sv = size 

2243 oh, ov = offset 

2244 shs, svs = Widget.get_min_size(self) 

2245 ghs, gvs = Widget.get_grow(self) 

2246 

2247 if ghs == 0.0: 

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

2249 sh = shs 

2250 

2251 if gvs == 0.0: 

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

2253 sv = svs 

2254 

2255 if self.aspect is not None: 

2256 if sh > sv/self.aspect: 

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

2258 sh = sv/self.aspect 

2259 if sv > sh*self.aspect: 

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

2261 sv = sh*self.aspect 

2262 

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

2264 

2265 def get_min_size(self): 

2266 

2267 ''' 

2268 Get minimum size of widget. 

2269 

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

2271 ''' 

2272 

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

2274 if self.aspect is not None: 

2275 if mv == 0.0: 

2276 return mh, mh*self.aspect 

2277 elif mh == 0.0: 

2278 return mv/self.aspect, mv 

2279 return mh, mv 

2280 

2281 def get_grow(self): 

2282 

2283 ''' 

2284 Get widget's desire to grow. 

2285 

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

2287 ''' 

2288 

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

2290 

2291 def set_size(self, size, offset): 

2292 

2293 ''' 

2294 Set the widget's current size. 

2295 

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

2297 responsibility to call this. 

2298 ''' 

2299 

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

2301 self.offset = inner_offset 

2302 self.horizontal.set_value(sh) 

2303 self.vertical.set_value(sv) 

2304 self.dirty = False 

2305 

2306 def __str__(self): 

2307 

2308 def indent(ind, str): 

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

2310 size, offset = self.get_size() 

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

2312 children = self.get_children() 

2313 if children: 

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

2315 return s 

2316 

2317 def policies_debug_str(self): 

2318 

2319 def indent(ind, str): 

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

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

2322 s = "%s: minimum=(%s, %s), grow=(%s, %s), aspect=%s\n" % ( 

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

2324 

2325 children = self.get_children() 

2326 if children: 

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

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

2329 return s 

2330 

2331 def get_corners(self, descend=False): 

2332 

2333 ''' 

2334 Get coordinates of the corners of the widget. 

2335 

2336 Returns list with coordinate tuples. 

2337 

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

2339 coordinates of all sub-widgets. 

2340 ''' 

2341 

2342 self.do_layout() 

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

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

2345 if descend: 

2346 for child in self.get_children(): 

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

2348 return corners 

2349 

2350 def get_sizes(self): 

2351 

2352 ''' 

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

2354 

2355 Returns a list with size tuples. 

2356 ''' 

2357 self.do_layout() 

2358 sizes = [self.get_size()] 

2359 for child in self.get_children(): 

2360 sizes.extend(child.get_sizes()) 

2361 return sizes 

2362 

2363 def do_layout(self): 

2364 

2365 ''' 

2366 Triggers layouting of the widget hierarchy, if needed. 

2367 ''' 

2368 

2369 if self.parent is not None: 

2370 return self.parent.do_layout() 

2371 

2372 if not self.dirty: 

2373 return 

2374 

2375 sh, sv = self.get_min_size() 

2376 gh, gv = self.get_grow() 

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

2378 sh = 15.*cm 

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

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

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

2382 

2383 def get_children(self): 

2384 

2385 ''' 

2386 Get sub-widgets contained in this widget. 

2387 

2388 Returns a list of widgets. 

2389 ''' 

2390 

2391 return [] 

2392 

2393 def get_size(self): 

2394 

2395 ''' 

2396 Get current size and position of the widget. 

2397 

2398 Triggers layouting and returns 

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

2400 ''' 

2401 

2402 self.do_layout() 

2403 return (self.horizontal.get_value(), 

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

2405 

2406 def get_params(self): 

2407 

2408 ''' 

2409 Get current size and position of the widget. 

2410 

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

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

2413 ''' 

2414 

2415 self.do_layout() 

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

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

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

2419 

2420 def width(self): 

2421 

2422 ''' 

2423 Get current width of the widget. 

2424 

2425 Triggers layouting and returns width. 

2426 ''' 

2427 

2428 self.do_layout() 

2429 return self.horizontal.get_value() 

2430 

2431 def height(self): 

2432 

2433 ''' 

2434 Get current height of the widget. 

2435 

2436 Triggers layouting and return height. 

2437 ''' 

2438 

2439 self.do_layout() 

2440 return self.vertical.get_value() 

2441 

2442 def bbox(self): 

2443 

2444 ''' 

2445 Get PostScript bounding box for this widget. 

2446 

2447 Triggers layouting and returns values suitable to create PS bounding 

2448 box, representing the widgets current size and position. 

2449 ''' 

2450 

2451 self.do_layout() 

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

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

2454 

2455 def dirtyfy(self): 

2456 

2457 ''' 

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

2459 

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

2461 new layouting. 

2462 ''' 

2463 

2464 if self.parent is not None: 

2465 self.parent.dirtyfy() 

2466 

2467 self.dirty = True 

2468 

2469 

2470class CenterLayout(Widget): 

2471 

2472 ''' 

2473 A layout manager which centers its single child widget. 

2474 

2475 The child widget may be oversized. 

2476 ''' 

2477 

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

2479 Widget.__init__(self, horizontal, vertical) 

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

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

2482 

2483 def get_min_size(self): 

2484 shs, svs = Widget.get_min_size(self) 

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

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

2487 

2488 def get_grow(self): 

2489 ghs, gvs = Widget.get_grow(self) 

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

2491 return gh*ghs, gv*gvs 

2492 

2493 def set_size(self, size, offset): 

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

2495 

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

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

2498 if ghc != 0.: 

2499 shc = sh 

2500 if gvc != 0.: 

2501 svc = sv 

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

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

2504 

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

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

2507 

2508 def set_widget(self, widget=None): 

2509 

2510 ''' 

2511 Set the child widget, which shall be centered. 

2512 ''' 

2513 

2514 if widget is None: 

2515 widget = Widget() 

2516 

2517 self.content = widget 

2518 

2519 widget.set_parent(self) 

2520 

2521 def get_widget(self): 

2522 return self.content 

2523 

2524 def get_children(self): 

2525 return [self.content] 

2526 

2527 

2528class FrameLayout(Widget): 

2529 

2530 ''' 

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

2532 widgets. 

2533 

2534 :: 

2535 

2536 +---------------------------+ 

2537 | top | 

2538 +---------------------------+ 

2539 | | | | 

2540 | left | center | right | 

2541 | | | | 

2542 +---------------------------+ 

2543 | bottom | 

2544 +---------------------------+ 

2545 

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

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

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

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

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

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

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

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

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

2555 spaces between the widgets. 

2556 ''' 

2557 

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

2559 Widget.__init__(self, horizontal, vertical) 

2560 mw = 3.*cm 

2561 self.left = Widget( 

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

2563 self.right = Widget( 

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

2565 self.top = Widget( 

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

2567 parent=self) 

2568 self.bottom = Widget( 

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

2570 parent=self) 

2571 self.center = Widget( 

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

2573 parent=self) 

2574 

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

2576 ''' 

2577 Give margins fixed size constraints. 

2578 ''' 

2579 

2580 self.left.set_horizontal(left, 0) 

2581 self.right.set_horizontal(right, 0) 

2582 self.top.set_vertical(top, 0) 

2583 self.bottom.set_vertical(bottom, 0) 

2584 

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

2586 ''' 

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

2588 

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

2590 ''' 

2591 self.left.set_horizontal(left, grow) 

2592 self.right.set_horizontal(right, grow) 

2593 self.top.set_vertical(top, grow) 

2594 self.bottom.set_vertical(bottom, grow) 

2595 

2596 def get_min_size(self): 

2597 shs, svs = Widget.get_min_size(self) 

2598 

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

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

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

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

2603 

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

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

2606 

2607 # prevent widgets from collapsing 

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

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

2610 shsum += 0.1*cm 

2611 

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

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

2614 svsum += 0.1*cm 

2615 

2616 sh = max(shs, shsum) 

2617 sv = max(svs, svsum) 

2618 

2619 return sh, sv 

2620 

2621 def get_grow(self): 

2622 ghs, gvs = Widget.get_grow(self) 

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

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

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

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

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

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

2629 return gh, gv 

2630 

2631 def set_size(self, size, offset): 

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

2633 

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

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

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

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

2638 

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

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

2641 

2642 if ah < 0.0: 

2643 raise GmtPyError("Container not wide enough for contents " 

2644 "(FrameLayout, available: %g cm, needed: %g cm)" 

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

2646 if av < 0.0: 

2647 raise GmtPyError("Container not high enough for contents " 

2648 "(FrameLayout, available: %g cm, needed: %g cm)" 

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

2650 

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

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

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

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

2655 

2656 if self.center.aspect is not None: 

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

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

2659 if 0.0 < ahm < ah: 

2660 slh, srh, sch = distribute( 

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

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

2663 

2664 elif 0.0 < avm < av: 

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

2666 sch*self.center.aspect), 

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

2668 

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

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

2671 

2672 oh += ah/2. 

2673 ov += av/2. 

2674 sh -= ah 

2675 sv -= av 

2676 

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

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

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

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

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

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

2683 

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

2685 

2686 ''' 

2687 Set one of the sub-widgets. 

2688 

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

2690 ``'bottom'`` or ``'center'``. 

2691 ''' 

2692 

2693 if widget is None: 

2694 widget = Widget() 

2695 

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

2697 self.__dict__[which] = widget 

2698 else: 

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

2700 

2701 widget.set_parent(self) 

2702 

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

2704 

2705 ''' 

2706 Get one of the sub-widgets. 

2707 

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

2709 ``'bottom'`` or ``'center'``. 

2710 ''' 

2711 

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

2713 return self.__dict__[which] 

2714 else: 

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

2716 

2717 def get_children(self): 

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

2719 

2720 

2721class GridLayout(Widget): 

2722 

2723 ''' 

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

2725 

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

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

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

2729 

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

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

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

2733 might not be resolved optimally. 

2734 ''' 

2735 

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

2737 

2738 ''' 

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

2740 ''' 

2741 

2742 Widget.__init__(self, horizontal, vertical) 

2743 self.grid = [] 

2744 for iy in range(ny): 

2745 row = [] 

2746 for ix in range(nx): 

2747 w = Widget(parent=self) 

2748 row.append(w) 

2749 

2750 self.grid.append(row) 

2751 

2752 def sub_min_sizes_as_array(self): 

2753 esh = num.array( 

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

2755 dtype=float) 

2756 esv = num.array( 

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

2758 dtype=float) 

2759 return esh, esv 

2760 

2761 def sub_grows_as_array(self): 

2762 egh = num.array( 

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

2764 dtype=float) 

2765 egv = num.array( 

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

2767 dtype=float) 

2768 return egh, egv 

2769 

2770 def get_min_size(self): 

2771 sh, sv = Widget.get_min_size(self) 

2772 esh, esv = self.sub_min_sizes_as_array() 

2773 if esh.size != 0: 

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

2775 if esv.size != 0: 

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

2777 return sh, sv 

2778 

2779 def get_grow(self): 

2780 ghs, gvs = Widget.get_grow(self) 

2781 egh, egv = self.sub_grows_as_array() 

2782 if egh.size != 0: 

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

2784 else: 

2785 gh = 1.0 

2786 if egv.size != 0: 

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

2788 else: 

2789 gv = 1.0 

2790 return gh, gv 

2791 

2792 def set_size(self, size, offset): 

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

2794 esh, esv = self.sub_min_sizes_as_array() 

2795 egh, egv = self.sub_grows_as_array() 

2796 

2797 # available additional space 

2798 empty = esh.size == 0 

2799 

2800 if not empty: 

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

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

2803 else: 

2804 av = sv 

2805 ah = sh 

2806 

2807 if ah < 0.0: 

2808 raise GmtPyError("Container not wide enough for contents " 

2809 "(GridLayout, available: %g cm, needed: %g cm)" 

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

2811 if av < 0.0: 

2812 raise GmtPyError("Container not high enough for contents " 

2813 "(GridLayout, available: %g cm, needed: %g cm)" 

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

2815 

2816 nx, ny = esh.shape 

2817 

2818 if not empty: 

2819 # distribute additional space on rows and columns 

2820 # according to grow weights and minimal sizes 

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

2822 nesh = esh.copy() 

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

2824 

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

2826 

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

2828 nesv = esv.copy() 

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

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

2831 

2832 ah = sh - sum(nsh) 

2833 av = sv - sum(nsv) 

2834 

2835 oh += ah/2. 

2836 ov += av/2. 

2837 sh -= ah 

2838 sv -= av 

2839 

2840 # resize child widgets 

2841 neov = ov + sum(nsv) 

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

2843 neov -= nesv 

2844 neoh = oh 

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

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

2847 neoh += nesh 

2848 

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

2850 

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

2852 

2853 ''' 

2854 Set one of the sub-widgets. 

2855 

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

2857 counted from zero. 

2858 ''' 

2859 

2860 if widget is None: 

2861 widget = Widget() 

2862 

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

2864 widget.set_parent(self) 

2865 

2866 def get_widget(self, ix, iy): 

2867 

2868 ''' 

2869 Get one of the sub-widgets. 

2870 

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

2872 counted from zero. 

2873 ''' 

2874 

2875 return self.grid[iy][ix] 

2876 

2877 def get_children(self): 

2878 children = [] 

2879 for row in self.grid: 

2880 children.extend(row) 

2881 

2882 return children 

2883 

2884 

2885def is_gmt5(version='newest'): 

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

2887 

2888 

2889def is_gmt6(version='newest'): 

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

2891 

2892 

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

2894 

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

2896 

2897 if gmt.is_gmt5(): 

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

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

2900 gmt.save(fn, crop_eps_mode=True) 

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

2902 s = f.read() 

2903 

2904 l, b, r, t = get_bbox(s) 

2905 else: 

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

2907 l, b, r, t = gmt.bbox() 

2908 

2909 return (t-b)/(r-l) 

2910 

2911 

2912def text_box( 

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

2914 

2915 gmt = GMT(version=gmtversion) 

2916 if gmt.is_gmt5(): 

2917 row = [0, 0, text] 

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

2919 else: 

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

2921 farg = [] 

2922 

2923 gmt.pstext( 

2924 in_rows=[row], 

2925 finish=True, 

2926 R=(0, 1, 0, 1), 

2927 J='x10p', 

2928 N=True, 

2929 *farg, 

2930 **kwargs) 

2931 

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

2933 gmt.save(fn) 

2934 

2935 (_, stderr) = subprocess.Popen( 

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

2937 stderr=subprocess.PIPE).communicate() 

2938 

2939 dx, dy = None, None 

2940 for line in stderr.splitlines(): 

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

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

2943 dx, dy = r-l, t-b 

2944 break 

2945 

2946 return dx, dy 

2947 

2948 

2949class TableLiner(object): 

2950 ''' 

2951 Utility class to turn tables into lines. 

2952 ''' 

2953 

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

2955 self.in_columns = in_columns 

2956 self.in_rows = in_rows 

2957 self.encoding = encoding 

2958 

2959 def __iter__(self): 

2960 if self.in_columns is not None: 

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

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

2963 self.encoding) 

2964 

2965 if self.in_rows is not None: 

2966 for row in self.in_rows: 

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

2968 self.encoding) 

2969 

2970 

2971class LineStreamChopper(object): 

2972 ''' 

2973 File-like object to buffer data. 

2974 ''' 

2975 

2976 def __init__(self, liner): 

2977 self.chopsize = None 

2978 self.liner = liner 

2979 self.chop_iterator = None 

2980 self.closed = False 

2981 

2982 def _chopiter(self): 

2983 buf = BytesIO() 

2984 for line in self.liner: 

2985 buf.write(line) 

2986 buflen = buf.tell() 

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

2988 buf.seek(0) 

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

2990 yield buf.read(self.chopsize) 

2991 

2992 newbuf = BytesIO() 

2993 newbuf.write(buf.read()) 

2994 buf.close() 

2995 buf = newbuf 

2996 

2997 yield(buf.getvalue()) 

2998 buf.close() 

2999 

3000 def read(self, size=None): 

3001 if self.closed: 

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

3003 if self.chop_iterator is None: 

3004 self.chopsize = size 

3005 self.chop_iterator = self._chopiter() 

3006 

3007 self.chopsize = size 

3008 try: 

3009 return next(self.chop_iterator) 

3010 except StopIteration: 

3011 return '' 

3012 

3013 def close(self): 

3014 self.chopsize = None 

3015 self.chop_iterator = None 

3016 self.closed = True 

3017 

3018 def flush(self): 

3019 pass 

3020 

3021 

3022font_tab = { 

3023 0: 'Helvetica', 

3024 1: 'Helvetica-Bold', 

3025} 

3026 

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

3028 

3029 

3030class GMT(object): 

3031 ''' 

3032 A thin wrapper to GMT command execution. 

3033 

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

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

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

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

3038 gmtpy and gmtpy must know where to find it. 

3039 

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

3041 output file. 

3042 

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

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

3045 

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

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

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

3049 

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

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

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

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

3054 

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

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

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

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

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

3060 execution of more than one GMT instance. 

3061 

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

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

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

3065 backward compatibility of the scripts can be maintained. 

3066 

3067 ''' 

3068 

3069 def __init__( 

3070 self, 

3071 config=None, 

3072 kontinue=None, 

3073 version='newest', 

3074 config_papersize=None, 

3075 eps_mode=False): 

3076 

3077 self.installation = get_gmt_installation(version) 

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

3079 self.eps_mode = eps_mode 

3080 self._shutil = shutil 

3081 

3082 if config: 

3083 self.gmt_config.update(config) 

3084 

3085 if config_papersize: 

3086 if not isinstance(config_papersize, str): 

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

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

3089 

3090 if self.is_gmt5(): 

3091 self.gmt_config['PS_MEDIA'] = config_papersize 

3092 else: 

3093 self.gmt_config['PAPER_MEDIA'] = config_papersize 

3094 

3095 self.tempdir = tempfile.mkdtemp("", "gmtpy-") 

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

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

3098 

3099 if kontinue is not None: 

3100 self.load_unfinished(kontinue) 

3101 self.needstart = False 

3102 else: 

3103 self.output = BytesIO() 

3104 self.needstart = True 

3105 

3106 self.finished = False 

3107 

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

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

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

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

3112 

3113 self.layout = None 

3114 self.command_log = [] 

3115 self.keep_temp_dir = False 

3116 

3117 def is_gmt5(self): 

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

3119 

3120 def is_gmt6(self): 

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

3122 

3123 def get_version(self): 

3124 return self.installation['version'] 

3125 

3126 def get_config(self, key): 

3127 return self.gmt_config[key] 

3128 

3129 def to_points(self, string): 

3130 if not string: 

3131 return 0 

3132 

3133 unit = string[-1] 

3134 if unit in _units: 

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

3136 else: 

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

3138 return float(string)/_units[default_unit] 

3139 

3140 def label_font_size(self): 

3141 if self.is_gmt5(): 

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

3143 else: 

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

3145 

3146 def label_font(self): 

3147 if self.is_gmt5(): 

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

3149 else: 

3150 return self.gmt_config['LABEL_FONT'] 

3151 

3152 def gen_gmt_config_file(self, config_filename, config): 

3153 f = open(config_filename, 'wb') 

3154 f.write( 

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

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

3157 

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

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

3160 f.close() 

3161 

3162 def __del__(self): 

3163 if not self.keep_temp_dir: 

3164 self._shutil.rmtree(self.tempdir) 

3165 

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

3167 

3168 ''' 

3169 Execute arbitrary GMT command. 

3170 

3171 See docstring in __getattr__ for details. 

3172 ''' 

3173 

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

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

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

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

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

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

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

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

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

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

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

3185 

3186 assert(not self.finished) 

3187 

3188 # check for mutual exclusiveness on input and output possibilities 

3189 assert(1 >= len( 

3190 [x for x in [ 

3191 in_stream, in_filename, in_string, in_columns, in_rows] 

3192 if x is not None])) 

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

3194 if x is not None])) 

3195 

3196 options = [] 

3197 

3198 gmt_config = self.gmt_config 

3199 if not self.is_gmt5(): 

3200 gmt_config_filename = self.gmt_config_filename 

3201 if config_override: 

3202 gmt_config = self.gmt_config.copy() 

3203 gmt_config.update(config_override) 

3204 gmt_config_override_filename = pjoin( 

3205 self.tempdir, 'gmtdefaults_override') 

3206 self.gen_gmt_config_file( 

3207 gmt_config_override_filename, gmt_config) 

3208 gmt_config_filename = gmt_config_override_filename 

3209 

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

3211 if config_override: 

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

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

3214 

3215 if out_discard: 

3216 out_filename = '/dev/null' 

3217 

3218 out_mustclose = False 

3219 if out_filename is not None: 

3220 out_mustclose = True 

3221 out_stream = open(out_filename, 'wb') 

3222 

3223 if in_filename is not None: 

3224 in_stream = open(in_filename, 'rb') 

3225 

3226 if in_string is not None: 

3227 in_stream = BytesIO(in_string) 

3228 

3229 encoding_gmt = gmt_config.get( 

3230 'PS_CHAR_ENCODING', 

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

3232 

3233 encoding = encoding_gmt_to_python[encoding_gmt.lower()] 

3234 

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

3236 in_stream = LineStreamChopper(TableLiner(in_columns=in_columns, 

3237 in_rows=in_rows, 

3238 encoding=encoding)) 

3239 

3240 # convert option arguments to strings 

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

3242 if len(k) > 1: 

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

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

3245 % (k, command)) 

3246 

3247 if type(v) is bool: 

3248 if v: 

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

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

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

3252 else: 

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

3254 

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

3256 if out_stream is None: 

3257 if not finish: 

3258 options.append('-K') 

3259 else: 

3260 self.finished = True 

3261 

3262 if not self.needstart: 

3263 options.append('-O') 

3264 else: 

3265 self.needstart = False 

3266 

3267 out_stream = self.output 

3268 

3269 # run the command 

3270 if self.is_gmt5(): 

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

3272 else: 

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

3274 

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

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

3277 args.extend(options) 

3278 args.extend(addargs) 

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

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

3281 args.append('+'+gmt_config_filename) 

3282 

3283 bs = 2048 

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

3285 stdout=subprocess.PIPE, bufsize=bs, 

3286 env=self.environ) 

3287 while True: 

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

3289 if cr: 

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

3291 if cw: 

3292 if in_stream is not None: 

3293 data = in_stream.read(bs) 

3294 if len(data) == 0: 

3295 break 

3296 p.stdin.write(data) 

3297 else: 

3298 break 

3299 if not cr and not cw: 

3300 break 

3301 

3302 p.stdin.close() 

3303 

3304 while True: 

3305 data = p.stdout.read(bs) 

3306 if len(data) == 0: 

3307 break 

3308 out_stream.write(data) 

3309 

3310 p.stdout.close() 

3311 

3312 retcode = p.wait() 

3313 

3314 if in_stream is not None: 

3315 in_stream.close() 

3316 

3317 if out_mustclose: 

3318 out_stream.close() 

3319 

3320 if retcode != 0: 

3321 self.keep_temp_dir = True 

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

3323 'While executing command:\n%s' 

3324 % (command, escape_shell_args(args))) 

3325 

3326 self.command_log.append(args) 

3327 

3328 def __getattr__(self, command): 

3329 

3330 ''' 

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

3332 

3333 Execute arbitrary GMT command. 

3334 

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

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

3337 called. 

3338 

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

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

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

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

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

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

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

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

3347 

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

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

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

3351 not interested in the output. 

3352 

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

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

3355 

3356 =============== ======================================================= 

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

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

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

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

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

3362 ascii 

3363 table, which is fed to the process. 

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

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

3366 table, which is fed to the process. 

3367 =============== ======================================================= 

3368 

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

3370 following options: 

3371 

3372 ================= ===================================================== 

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

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

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

3376 ================= ===================================================== 

3377 

3378 Additional keyword arguments: 

3379 

3380 ===================== ================================================= 

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

3382 currently active set of defaults exclusively 

3383 during this call. 

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

3385 by the GMT instance is finished, and no further 

3386 plotting is allowed. 

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

3388 option to the command. 

3389 ===================== ================================================= 

3390 

3391 ''' 

3392 

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

3394 return self._gmtcommand(command, *args, **kwargs) 

3395 return f 

3396 

3397 def tempfilename(self, name=None): 

3398 ''' 

3399 Get filename for temporary file in the private temp directory. 

3400 

3401 If no ``name`` argument is given, a random name is picked. If 

3402 ``name`` is given, returns a path ending in that ``name``. 

3403 ''' 

3404 

3405 if not name: 

3406 name = ''.join( 

3407 [random.choice('abcdefghijklmnopqrstuvwxyz') 

3408 for i in range(10)]) 

3409 

3410 fn = pjoin(self.tempdir, name) 

3411 return fn 

3412 

3413 def tempfile(self, name=None): 

3414 ''' 

3415 Create and open a file in the private temp directory. 

3416 ''' 

3417 

3418 fn = self.tempfilename(name) 

3419 f = open(fn, 'wb') 

3420 return f, fn 

3421 

3422 def save_unfinished(self, filename): 

3423 out = open(filename, 'wb') 

3424 out.write(self.output.getvalue()) 

3425 out.close() 

3426 

3427 def load_unfinished(self, filename): 

3428 self.output = BytesIO() 

3429 self.finished = False 

3430 inp = open(filename, 'rb') 

3431 self.output.write(inp.read()) 

3432 inp.close() 

3433 

3434 def dump(self, ident): 

3435 filename = self.tempfilename('breakpoint-%s' % ident) 

3436 self.save_unfinished(filename) 

3437 

3438 def load(self, ident): 

3439 filename = self.tempfilename('breakpoint-%s' % ident) 

3440 self.load_unfinished(filename) 

3441 

3442 def save(self, filename=None, bbox=None, resolution=150, oversample=2., 

3443 width=None, height=None, size=None, crop_eps_mode=False, 

3444 psconvert=False): 

3445 

3446 ''' 

3447 Finish and save figure as PDF, PS or PPM file. 

3448 

3449 If filename ends with ``'.pdf'`` a PDF file is created by piping the 

3450 GMT output through :program:`gmtpy-epstopdf`. 

3451 

3452 If filename ends with ``'.png'`` a PNG file is created by running 

3453 :program:`gmtpy-epstopdf`, :program:`pdftocairo` and 

3454 :program:`convert`. ``resolution`` specifies the resolution in DPI for 

3455 raster file formats. Rasterization is done at a higher resolution if 

3456 ``oversample`` is set to a value higher than one. The output image size 

3457 can also be controlled by setting ``width``, ``height`` or ``size`` 

3458 instead of ``resolution``. When ``size`` is given, the image is scaled 

3459 so that ``max(width, height) == size``. 

3460 

3461 The bounding box is set according to the values given in ``bbox``. 

3462 ''' 

3463 

3464 if not self.finished: 

3465 self.psxy(R=True, J=True, finish=True) 

3466 

3467 if filename: 

3468 tempfn = pjoin(self.tempdir, 'incomplete') 

3469 out = open(tempfn, 'wb') 

3470 else: 

3471 out = sys.stdout 

3472 

3473 if bbox and not self.is_gmt5(): 

3474 out.write(replace_bbox(bbox, self.output.getvalue())) 

3475 else: 

3476 out.write(self.output.getvalue()) 

3477 

3478 if filename: 

3479 out.close() 

3480 

3481 if filename.endswith('.ps') or ( 

3482 not self.is_gmt5() and filename.endswith('.eps')): 

3483 

3484 shutil.move(tempfn, filename) 

3485 return 

3486 

3487 if self.is_gmt5(): 

3488 if crop_eps_mode: 

3489 addarg = ['-A'] 

3490 else: 

3491 addarg = [] 

3492 

3493 subprocess.call( 

3494 [pjoin(self.installation['bin'], 'gmt'), 'psconvert', 

3495 '-Te', '-F%s' % tempfn, tempfn, ] + addarg) 

3496 

3497 if bbox: 

3498 with open(tempfn + '.eps', 'rb') as fin: 

3499 with open(tempfn + '-fixbb.eps', 'wb') as fout: 

3500 replace_bbox(bbox, fin, fout) 

3501 

3502 shutil.move(tempfn + '-fixbb.eps', tempfn + '.eps') 

3503 

3504 else: 

3505 shutil.move(tempfn, tempfn + '.eps') 

3506 

3507 if filename.endswith('.eps'): 

3508 shutil.move(tempfn + '.eps', filename) 

3509 return 

3510 

3511 elif filename.endswith('.pdf'): 

3512 if psconvert: 

3513 gmt_bin = pjoin(self.installation['bin'], 'gmt') 

3514 subprocess.call([gmt_bin, 'psconvert', tempfn + '.eps', '-Tf', 

3515 '-F' + filename]) 

3516 else: 

3517 subprocess.call(['gmtpy-epstopdf', '--res=%i' % resolution, 

3518 '--outfile=' + filename, tempfn + '.eps']) 

3519 else: 

3520 subprocess.call([ 

3521 'gmtpy-epstopdf', 

3522 '--res=%i' % (resolution * oversample), 

3523 '--outfile=' + tempfn + '.pdf', tempfn + '.eps']) 

3524 

3525 convert_graph( 

3526 tempfn + '.pdf', filename, 

3527 resolution=resolution, oversample=oversample, 

3528 size=size, width=width, height=height) 

3529 

3530 def bbox(self): 

3531 return get_bbox(self.output.getvalue()) 

3532 

3533 def get_command_log(self): 

3534 ''' 

3535 Get the command log. 

3536 ''' 

3537 

3538 return self.command_log 

3539 

3540 def __str__(self): 

3541 s = '' 

3542 for com in self.command_log: 

3543 s += com[0] + "\n " + "\n ".join(com[1:]) + "\n\n" 

3544 return s 

3545 

3546 def page_size_points(self): 

3547 ''' 

3548 Try to get paper size of output postscript file in points. 

3549 ''' 

3550 

3551 pm = paper_media(self.gmt_config).lower() 

3552 if pm.endswith('+') or pm.endswith('-'): 

3553 pm = pm[:-1] 

3554 

3555 orient = page_orientation(self.gmt_config).lower() 

3556 

3557 if pm in all_paper_sizes(): 

3558 

3559 if orient == 'portrait': 

3560 return get_paper_size(pm) 

3561 else: 

3562 return get_paper_size(pm)[1], get_paper_size(pm)[0] 

3563 

3564 m = re.match(r'custom_([0-9.]+)([cimp]?)x([0-9.]+)([cimp]?)', pm) 

3565 if m: 

3566 w, uw, h, uh = m.groups() 

3567 w, h = float(w), float(h) 

3568 if uw: 

3569 w *= _units[uw] 

3570 if uh: 

3571 h *= _units[uh] 

3572 if orient == 'portrait': 

3573 return w, h 

3574 else: 

3575 return h, w 

3576 

3577 return None, None 

3578 

3579 def default_layout(self, with_palette=False): 

3580 ''' 

3581 Get a default layout for the output page. 

3582 

3583 One of three different layouts is choosen, depending on the 

3584 `PAPER_MEDIA` setting in the GMT configuration dict. 

3585 

3586 If `PAPER_MEDIA` ends with a ``'+'`` (EPS output is selected), a 

3587 :py:class:`FrameLayout` is centered on the page, whose size is 

3588 controlled by its center widget's size plus the margins of the 

3589 :py:class:`FrameLayout`. 

3590 

3591 If `PAPER_MEDIA` indicates, that a custom page size is wanted by 

3592 starting with ``'Custom_'``, a :py:class:`FrameLayout` is used to fill 

3593 the complete page. The center widget's size is then controlled by the 

3594 page's size minus the margins of the :py:class:`FrameLayout`. 

3595 

3596 In any other case, two FrameLayouts are nested, such that the outer 

3597 layout attaches a 1 cm (printer) margin around the complete page, and 

3598 the inner FrameLayout's center widget takes up as much space as 

3599 possible under the constraint, that an aspect ratio of 1/golden_ratio 

3600 is preserved. 

3601 

3602 In any case, a reference to the innermost :py:class:`FrameLayout` 

3603 instance is returned. The top-level layout can be accessed by calling 

3604 :py:meth:`Widget.get_parent` on the returned layout. 

3605 ''' 

3606 

3607 if self.layout is None: 

3608 w, h = self.page_size_points() 

3609 

3610 if w is None or h is None: 

3611 raise GmtPyError("Can't determine page size for layout") 

3612 

3613 pm = paper_media(self.gmt_config).lower() 

3614 

3615 if with_palette: 

3616 palette_layout = GridLayout(3, 1) 

3617 spacer = palette_layout.get_widget(1, 0) 

3618 palette_widget = palette_layout.get_widget(2, 0) 

3619 spacer.set_horizontal(0.5*cm) 

3620 palette_widget.set_horizontal(0.5*cm) 

3621 

3622 if pm.endswith('+') or self.eps_mode: 

3623 outer = CenterLayout() 

3624 outer.set_policy((w, h), (0., 0.)) 

3625 inner = FrameLayout() 

3626 outer.set_widget(inner) 

3627 if with_palette: 

3628 inner.set_widget('center', palette_layout) 

3629 widget = palette_layout 

3630 else: 

3631 widget = inner.get_widget('center') 

3632 widget.set_policy((w/golden_ratio, 0.), (0., 0.), 

3633 aspect=1./golden_ratio) 

3634 mw = 3.0*cm 

3635 inner.set_fixed_margins( 

3636 mw, mw, mw/golden_ratio, mw/golden_ratio) 

3637 self.layout = inner 

3638 

3639 elif pm.startswith('custom_'): 

3640 layout = FrameLayout() 

3641 layout.set_policy((w, h), (0., 0.)) 

3642 mw = 3.0*cm 

3643 layout.set_min_margins( 

3644 mw, mw, mw/golden_ratio, mw/golden_ratio) 

3645 if with_palette: 

3646 layout.set_widget('center', palette_layout) 

3647 self.layout = layout 

3648 else: 

3649 outer = FrameLayout() 

3650 outer.set_policy((w, h), (0., 0.)) 

3651 outer.set_fixed_margins(1.*cm, 1.*cm, 1.*cm, 1.*cm) 

3652 

3653 inner = FrameLayout() 

3654 outer.set_widget('center', inner) 

3655 mw = 3.0*cm 

3656 inner.set_min_margins(mw, mw, mw/golden_ratio, mw/golden_ratio) 

3657 if with_palette: 

3658 inner.set_widget('center', palette_layout) 

3659 widget = palette_layout 

3660 else: 

3661 widget = inner.get_widget('center') 

3662 

3663 widget.set_aspect(1./golden_ratio) 

3664 

3665 self.layout = inner 

3666 

3667 return self.layout 

3668 

3669 def draw_layout(self, layout): 

3670 ''' 

3671 Use psxy to draw layout; for debugging 

3672 ''' 

3673 

3674 # corners = layout.get_corners(descend=True) 

3675 rects = num.array(layout.get_sizes(), dtype=float) 

3676 rects_wid = rects[:, 0, 0] 

3677 rects_hei = rects[:, 0, 1] 

3678 rects_center_x = rects[:, 1, 0] + rects_wid*0.5 

3679 rects_center_y = rects[:, 1, 1] + rects_hei*0.5 

3680 nrects = len(rects) 

3681 prects = (rects_center_x, rects_center_y, num.arange(nrects), 

3682 num.zeros(nrects), rects_hei, rects_wid) 

3683 

3684 # points = num.array(corners, dtype=float) 

3685 

3686 cptfile = self.tempfilename() + '.cpt' 

3687 self.makecpt( 

3688 C='ocean', 

3689 T='%g/%g/%g' % (-nrects, nrects, 1), 

3690 Z=True, 

3691 out_filename=cptfile, suppress_defaults=True) 

3692 

3693 bb = layout.bbox() 

3694 self.psxy( 

3695 in_columns=prects, 

3696 C=cptfile, 

3697 W='1p', 

3698 S='J', 

3699 R=(bb[0], bb[2], bb[1], bb[3]), 

3700 *layout.XYJ()) 

3701 

3702 

3703def simpleconf_to_ax(conf, axname): 

3704 c = {} 

3705 x = axname 

3706 for x in ('', axname): 

3707 for k in ('label', 'unit', 'scaled_unit', 'scaled_unit_factor', 

3708 'space', 'mode', 'approx_ticks', 'limits', 'masking', 'inc', 

3709 'snap'): 

3710 

3711 if x+k in conf: 

3712 c[k] = conf[x+k] 

3713 

3714 return Ax(**c) 

3715 

3716 

3717class DensityPlotDef(object): 

3718 def __init__(self, data, cpt='ocean', tension=0.7, size=(640, 480), 

3719 contour=False, method='surface', zscaler=None, **extra): 

3720 self.data = data 

3721 self.cpt = cpt 

3722 self.tension = tension 

3723 self.size = size 

3724 self.contour = contour 

3725 self.method = method 

3726 self.zscaler = zscaler 

3727 self.extra = extra 

3728 

3729 

3730class TextDef(object): 

3731 def __init__( 

3732 self, 

3733 data, 

3734 size=9, 

3735 justify='MC', 

3736 fontno=0, 

3737 offset=(0, 0), 

3738 color='black'): 

3739 

3740 self.data = data 

3741 self.size = size 

3742 self.justify = justify 

3743 self.fontno = fontno 

3744 self.offset = offset 

3745 self.color = color 

3746 

3747 

3748class Simple(object): 

3749 def __init__(self, gmtconfig=None, gmtversion='newest', **simple_config): 

3750 self.data = [] 

3751 self.symbols = [] 

3752 self.config = copy.deepcopy(simple_config) 

3753 self.gmtconfig = gmtconfig 

3754 self.density_plot_defs = [] 

3755 self.text_defs = [] 

3756 

3757 self.gmtversion = gmtversion 

3758 

3759 self.data_x = [] 

3760 self.symbols_x = [] 

3761 

3762 self.data_y = [] 

3763 self.symbols_y = [] 

3764 

3765 self.default_config = {} 

3766 self.set_defaults(width=15.*cm, 

3767 height=15.*cm / golden_ratio, 

3768 margins=(2.*cm, 2.*cm, 2.*cm, 2.*cm), 

3769 with_palette=False, 

3770 palette_offset=0.5*cm, 

3771 palette_width=None, 

3772 palette_height=None, 

3773 zlabeloffset=2*cm, 

3774 draw_layout=False) 

3775 

3776 self.setup_defaults() 

3777 self.fixate_widget_aspect = False 

3778 

3779 def setup_defaults(self): 

3780 pass 

3781 

3782 def set_defaults(self, **kwargs): 

3783 self.default_config.update(kwargs) 

3784 

3785 def plot(self, data, symbol=''): 

3786 self.data.append(data) 

3787 self.symbols.append(symbol) 

3788 

3789 def density_plot(self, data, **kwargs): 

3790 dpd = DensityPlotDef(data, **kwargs) 

3791 self.density_plot_defs.append(dpd) 

3792 

3793 def text(self, data, **kwargs): 

3794 dpd = TextDef(data, **kwargs) 

3795 self.text_defs.append(dpd) 

3796 

3797 def plot_x(self, data, symbol=''): 

3798 self.data_x.append(data) 

3799 self.symbols_x.append(symbol) 

3800 

3801 def plot_y(self, data, symbol=''): 

3802 self.data_y.append(data) 

3803 self.symbols_y.append(symbol) 

3804 

3805 def set(self, **kwargs): 

3806 self.config.update(kwargs) 

3807 

3808 def setup_base(self, conf): 

3809 w = conf.pop('width') 

3810 h = conf.pop('height') 

3811 margins = conf.pop('margins') 

3812 

3813 gmtconfig = {} 

3814 if self.gmtconfig is not None: 

3815 gmtconfig.update(self.gmtconfig) 

3816 

3817 gmt = GMT( 

3818 version=self.gmtversion, 

3819 config=gmtconfig, 

3820 config_papersize='Custom_%ix%i' % (w, h)) 

3821 

3822 layout = gmt.default_layout(with_palette=conf['with_palette']) 

3823 layout.set_min_margins(*margins) 

3824 if conf['with_palette']: 

3825 widget = layout.get_widget().get_widget(0, 0) 

3826 spacer = layout.get_widget().get_widget(1, 0) 

3827 spacer.set_horizontal(conf['palette_offset']) 

3828 palette_widget = layout.get_widget().get_widget(2, 0) 

3829 if conf['palette_width'] is not None: 

3830 palette_widget.set_horizontal(conf['palette_width']) 

3831 if conf['palette_height'] is not None: 

3832 palette_widget.set_vertical(conf['palette_height']) 

3833 widget.set_vertical(h-margins[2]-margins[3]-0.03*cm) 

3834 return gmt, layout, widget, palette_widget 

3835 else: 

3836 widget = layout.get_widget() 

3837 return gmt, layout, widget, None 

3838 

3839 def setup_projection(self, widget, scaler, conf): 

3840 pass 

3841 

3842 def setup_scaling(self, conf): 

3843 ndims = 2 

3844 if self.density_plot_defs: 

3845 ndims = 3 

3846 

3847 axes = [simpleconf_to_ax(conf, x) for x in 'xyz'[:ndims]] 

3848 

3849 data_all = [] 

3850 data_all.extend(self.data) 

3851 for dsd in self.density_plot_defs: 

3852 if dsd.zscaler is None: 

3853 data_all.append(dsd.data) 

3854 else: 

3855 data_all.append(dsd.data[:2]) 

3856 data_chopped = [ds[:ndims] for ds in data_all] 

3857 

3858 scaler = ScaleGuru(data_chopped, axes=axes[:ndims]) 

3859 

3860 self.setup_scaling_plus(scaler, axes[:ndims]) 

3861 

3862 return scaler 

3863 

3864 def setup_scaling_plus(self, scaler, axes): 

3865 pass 

3866 

3867 def setup_scaling_extra(self, scaler, conf): 

3868 

3869 scaler_x = scaler.copy() 

3870 scaler_x.data_ranges[1] = (0., 1.) 

3871 scaler_x.axes[1].mode = 'off' 

3872 

3873 scaler_y = scaler.copy() 

3874 scaler_y.data_ranges[0] = (0., 1.) 

3875 scaler_y.axes[0].mode = 'off' 

3876 

3877 return scaler_x, scaler_y 

3878 

3879 def draw_density(self, gmt, widget, scaler): 

3880 

3881 R = scaler.R() 

3882 # par = scaler.get_params() 

3883 rxyj = R + widget.XYJ() 

3884 innerticks = False 

3885 for dpd in self.density_plot_defs: 

3886 

3887 fn_cpt = gmt.tempfilename() + '.cpt' 

3888 

3889 if dpd.zscaler is not None: 

3890 s = dpd.zscaler 

3891 else: 

3892 s = scaler 

3893 

3894 gmt.makecpt(C=dpd.cpt, out_filename=fn_cpt, *s.T()) 

3895 

3896 fn_grid = gmt.tempfilename() 

3897 

3898 fn_mean = gmt.tempfilename() 

3899 

3900 if dpd.method in ('surface', 'triangulate'): 

3901 gmt.blockmean(in_columns=dpd.data, 

3902 I='%i+/%i+' % dpd.size, # noqa 

3903 out_filename=fn_mean, *R) 

3904 

3905 if dpd.method == 'surface': 

3906 gmt.surface( 

3907 in_filename=fn_mean, 

3908 T=dpd.tension, 

3909 G=fn_grid, 

3910 I='%i+/%i+' % dpd.size, # noqa 

3911 out_discard=True, 

3912 *R) 

3913 

3914 if dpd.method == 'triangulate': 

3915 gmt.triangulate( 

3916 in_filename=fn_mean, 

3917 G=fn_grid, 

3918 I='%i+/%i+' % dpd.size, # noqa 

3919 out_discard=True, 

3920 V=True, 

3921 *R) 

3922 

3923 if gmt.is_gmt5(): 

3924 gmt.grdimage(fn_grid, C=fn_cpt, E='i', n='l', *rxyj) 

3925 

3926 else: 

3927 gmt.grdimage(fn_grid, C=fn_cpt, E='i', S='l', *rxyj) 

3928 

3929 if dpd.contour: 

3930 gmt.grdcontour(fn_grid, C=fn_cpt, W='0.5p,black', *rxyj) 

3931 innerticks = '0.5p,black' 

3932 

3933 os.remove(fn_grid) 

3934 os.remove(fn_mean) 

3935 

3936 if dpd.method == 'fillcontour': 

3937 extra = dict(C=fn_cpt) 

3938 extra.update(dpd.extra) 

3939 gmt.pscontour(in_columns=dpd.data, 

3940 I=True, *rxyj, **extra) # noqa 

3941 

3942 if dpd.method == 'contour': 

3943 extra = dict(W='0.5p,black', C=fn_cpt) 

3944 extra.update(dpd.extra) 

3945 gmt.pscontour(in_columns=dpd.data, *rxyj, **extra) 

3946 

3947 return fn_cpt, innerticks 

3948 

3949 def draw_basemap(self, gmt, widget, scaler): 

3950 gmt.psbasemap(*(widget.JXY() + scaler.RB(ax_projection=True))) 

3951 

3952 def draw(self, gmt, widget, scaler): 

3953 rxyj = scaler.R() + widget.JXY() 

3954 for dat, sym in zip(self.data, self.symbols): 

3955 gmt.psxy(in_columns=dat, *(sym.split()+rxyj)) 

3956 

3957 def post_draw(self, gmt, widget, scaler): 

3958 pass 

3959 

3960 def pre_draw(self, gmt, widget, scaler): 

3961 pass 

3962 

3963 def draw_extra(self, gmt, widget, scaler_x, scaler_y): 

3964 

3965 for dat, sym in zip(self.data_x, self.symbols_x): 

3966 gmt.psxy(in_columns=dat, 

3967 *(sym.split() + scaler_x.R() + widget.JXY())) 

3968 

3969 for dat, sym in zip(self.data_y, self.symbols_y): 

3970 gmt.psxy(in_columns=dat, 

3971 *(sym.split() + scaler_y.R() + widget.JXY())) 

3972 

3973 def draw_text(self, gmt, widget, scaler): 

3974 

3975 rxyj = scaler.R() + widget.JXY() 

3976 for td in self.text_defs: 

3977 x, y = td.data[0:2] 

3978 text = td.data[-1] 

3979 size = td.size 

3980 angle = 0 

3981 fontno = td.fontno 

3982 justify = td.justify 

3983 color = td.color 

3984 if gmt.is_gmt5(): 

3985 gmt.pstext( 

3986 in_rows=[(x, y, text)], 

3987 F='+f%gp,%s,%s+a%g+j%s' % ( 

3988 size, fontno, color, angle, justify), 

3989 D='%gp/%gp' % td.offset, *rxyj) 

3990 else: 

3991 gmt.pstext( 

3992 in_rows=[(x, y, size, angle, fontno, justify, text)], 

3993 D='%gp/%gp' % td.offset, *rxyj) 

3994 

3995 def save(self, filename, resolution=150): 

3996 

3997 conf = dict(self.default_config) 

3998 conf.update(self.config) 

3999 

4000 gmt, layout, widget, palette_widget = self.setup_base(conf) 

4001 scaler = self.setup_scaling(conf) 

4002 scaler_x, scaler_y = self.setup_scaling_extra(scaler, conf) 

4003 

4004 self.setup_projection(widget, scaler, conf) 

4005 if self.fixate_widget_aspect: 

4006 aspect = aspect_for_projection( 

4007 gmt.installation['version'], *(widget.J() + scaler.R())) 

4008 

4009 widget.set_aspect(aspect) 

4010 

4011 if conf['draw_layout']: 

4012 gmt.draw_layout(layout) 

4013 cptfile = None 

4014 if self.density_plot_defs: 

4015 cptfile, innerticks = self.draw_density(gmt, widget, scaler) 

4016 self.pre_draw(gmt, widget, scaler) 

4017 self.draw(gmt, widget, scaler) 

4018 self.post_draw(gmt, widget, scaler) 

4019 self.draw_extra(gmt, widget, scaler_x, scaler_y) 

4020 self.draw_text(gmt, widget, scaler) 

4021 self.draw_basemap(gmt, widget, scaler) 

4022 

4023 if palette_widget and cptfile: 

4024 nice_palette(gmt, palette_widget, scaler, cptfile, 

4025 innerticks=innerticks, 

4026 zlabeloffset=conf['zlabeloffset']) 

4027 

4028 gmt.save(filename, resolution=resolution) 

4029 

4030 

4031class LinLinPlot(Simple): 

4032 pass 

4033 

4034 

4035class LogLinPlot(Simple): 

4036 

4037 def setup_defaults(self): 

4038 self.set_defaults(xmode='min-max') 

4039 

4040 def setup_projection(self, widget, scaler, conf): 

4041 widget['J'] = '-JX%(width)gpl/%(height)gp' 

4042 scaler['B'] = '-B2:%(xlabel)s:/%(yinc)g:%(ylabel)s:WSen' 

4043 

4044 

4045class LinLogPlot(Simple): 

4046 

4047 def setup_defaults(self): 

4048 self.set_defaults(ymode='min-max') 

4049 

4050 def setup_projection(self, widget, scaler, conf): 

4051 widget['J'] = '-JX%(width)gp/%(height)gpl' 

4052 scaler['B'] = '-B%(xinc)g:%(xlabel)s:/2:%(ylabel)s:WSen' 

4053 

4054 

4055class LogLogPlot(Simple): 

4056 

4057 def setup_defaults(self): 

4058 self.set_defaults(mode='min-max') 

4059 

4060 def setup_projection(self, widget, scaler, conf): 

4061 widget['J'] = '-JX%(width)gpl/%(height)gpl' 

4062 scaler['B'] = '-B2:%(xlabel)s:/2:%(ylabel)s:WSen' 

4063 

4064 

4065class AziDistPlot(Simple): 

4066 

4067 def __init__(self, *args, **kwargs): 

4068 Simple.__init__(self, *args, **kwargs) 

4069 self.fixate_widget_aspect = True 

4070 

4071 def setup_defaults(self): 

4072 self.set_defaults( 

4073 height=15.*cm, 

4074 width=15.*cm, 

4075 xmode='off', 

4076 xlimits=(0., 360.), 

4077 xinc=45.) 

4078 

4079 def setup_projection(self, widget, scaler, conf): 

4080 widget['J'] = '-JPa%(width)gp' 

4081 

4082 def setup_scaling_plus(self, scaler, axes): 

4083 scaler['B'] = '-B%(xinc)g:%(xlabel)s:/%(yinc)g:%(ylabel)s:N' 

4084 

4085 

4086class MPlot(Simple): 

4087 

4088 def __init__(self, *args, **kwargs): 

4089 Simple.__init__(self, *args, **kwargs) 

4090 self.fixate_widget_aspect = True 

4091 

4092 def setup_defaults(self): 

4093 self.set_defaults(xmode='min-max', ymode='min-max') 

4094 

4095 def setup_projection(self, widget, scaler, conf): 

4096 par = scaler.get_params() 

4097 lon0 = (par['xmin'] + par['xmax'])/2. 

4098 lat0 = (par['ymin'] + par['ymax'])/2. 

4099 sll = '%g/%g' % (lon0, lat0) 

4100 widget['J'] = '-JM' + sll + '/%(width)gp' 

4101 scaler['B'] = \ 

4102 '-B%(xinc)gg%(xinc)g:%(xlabel)s:/%(yinc)gg%(yinc)g:%(ylabel)s:WSen' 

4103 

4104 

4105def nice_palette(gmt, widget, scaleguru, cptfile, zlabeloffset=0.8*inch, 

4106 innerticks=True): 

4107 

4108 par = scaleguru.get_params() 

4109 par_ax = scaleguru.get_params(ax_projection=True) 

4110 nz_palette = int(widget.height()/inch * 300) 

4111 px = num.zeros(nz_palette*2) 

4112 px[1::2] += 1 

4113 pz = num.linspace(par['zmin'], par['zmax'], nz_palette).repeat(2) 

4114 pdz = pz[2]-pz[0] 

4115 palgrdfile = gmt.tempfilename() 

4116 pal_r = (0, 1, par['zmin'], par['zmax']) 

4117 pal_ax_r = (0, 1, par_ax['zmin'], par_ax['zmax']) 

4118 gmt.xyz2grd( 

4119 G=palgrdfile, R=pal_r, 

4120 I=(1, pdz), in_columns=(px, pz, pz), # noqa 

4121 out_discard=True) 

4122 

4123 gmt.grdimage(palgrdfile, R=pal_r, C=cptfile, *widget.JXY()) 

4124 if isinstance(innerticks, str): 

4125 tickpen = innerticks 

4126 gmt.grdcontour(palgrdfile, W=tickpen, R=pal_r, C=cptfile, 

4127 *widget.JXY()) 

4128 

4129 negpalwid = '%gp' % -widget.width() 

4130 if not isinstance(innerticks, str) and innerticks: 

4131 ticklen = negpalwid 

4132 else: 

4133 ticklen = '0p' 

4134 

4135 TICK_LENGTH_PARAM = 'MAP_TICK_LENGTH' if gmt.is_gmt5() else 'TICK_LENGTH' 

4136 gmt.psbasemap( 

4137 R=pal_ax_r, B='4::/%(zinc)g::nsw' % par_ax, 

4138 config={TICK_LENGTH_PARAM: ticklen}, 

4139 *widget.JXY()) 

4140 

4141 if innerticks: 

4142 gmt.psbasemap( 

4143 R=pal_ax_r, B='4::/%(zinc)g::E' % par_ax, 

4144 config={TICK_LENGTH_PARAM: '0p'}, 

4145 *widget.JXY()) 

4146 else: 

4147 gmt.psbasemap(R=pal_ax_r, B='4::/%(zinc)g::E' % par_ax, *widget.JXY()) 

4148 

4149 if par_ax['zlabel']: 

4150 label_font = gmt.label_font() 

4151 label_font_size = gmt.label_font_size() 

4152 label_offset = zlabeloffset 

4153 gmt.pstext( 

4154 R=(0, 1, 0, 2), D="%gp/0p" % label_offset, 

4155 N=True, 

4156 in_rows=[(1, 1, label_font_size, -90, label_font, 'CB', 

4157 par_ax['zlabel'])], 

4158 *widget.JXY())