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 

12import subprocess 

13try: 

14 from StringIO import StringIO as BytesIO 

15except ImportError: 

16 from io import BytesIO 

17import re 

18import os 

19import sys 

20import shutil 

21from os.path import join as pjoin 

22import tempfile 

23import random 

24import logging 

25import math 

26import numpy as num 

27import copy 

28from select import select 

29try: 

30 from scipy.io import netcdf_file 

31except ImportError: 

32 from scipy.io.netcdf import netcdf_file 

33 

34from pyrocko import ExternalProgramMissing 

35from . import AutoScaler 

36 

37 

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

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

40 

41 

42encoding_gmt_to_python = { 

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

44 'standard+': 'ascii', 

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

46 'standard': 'ascii'} 

47 

48for i in range(1, 11): 

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

50 

51 

52def have_gmt(): 

53 try: 

54 get_gmt_installation('newest') 

55 return True 

56 

57 except GMTInstallationProblem: 

58 return False 

59 

60 

61def check_have_gmt(): 

62 if not have_gmt(): 

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

64 

65 

66def have_pixmaptools(): 

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

68 try: 

69 p = subprocess.Popen( 

70 prog, 

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

72 

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

74 

75 except OSError: 

76 return False 

77 

78 return True 

79 

80 

81class GmtPyError(Exception): 

82 pass 

83 

84 

85class GMTError(GmtPyError): 

86 pass 

87 

88 

89class GMTInstallationProblem(GmtPyError): 

90 pass 

91 

92 

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

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

95 

96 _, tmp_filename_base = tempfile.mkstemp() 

97 

98 try: 

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

100 fmt_arg = '-svg' 

101 tmp_filename = tmp_filename_base 

102 oversample = 1.0 

103 else: 

104 fmt_arg = '-png' 

105 tmp_filename = tmp_filename_base + '-1.png' 

106 

107 if size is not None: 

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

109 elif width is not None: 

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

111 elif height is not None: 

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

113 else: 

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

115 

116 try: 

117 subprocess.check_call( 

118 ['pdftocairo'] + scale_args + 

119 [fmt_arg, in_filename, tmp_filename_base]) 

120 except OSError as e: 

121 raise GmtPyError( 

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

123 

124 if oversample > 1.: 

125 try: 

126 subprocess.check_call([ 

127 'convert', 

128 tmp_filename, 

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

130 out_filename]) 

131 except OSError as e: 

132 raise GmtPyError( 

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

134 

135 else: 

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

137 shutil.move(tmp_filename, out_filename) 

138 else: 

139 try: 

140 subprocess.check_call( 

141 ['convert', tmp_filename, out_filename]) 

142 except Exception as e: 

143 raise GmtPyError( 

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

145 % str(e)) 

146 

147 except Exception: 

148 raise 

149 

150 finally: 

151 if os.path.exists(tmp_filename_base): 

152 os.remove(tmp_filename_base) 

153 

154 if os.path.exists(tmp_filename): 

155 os.remove(tmp_filename) 

156 

157 

158def get_bbox(s): 

159 for pat in [find_hiresbb, find_bb]: 

160 m = pat.search(s) 

161 if m: 

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

163 return bb 

164 

165 raise GmtPyError('Cannot find bbox') 

166 

167 

168def replace_bbox(bbox, *args): 

169 

170 def repl(m): 

171 if m.group(1): 

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

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

174 else: 

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

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

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

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

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

180 

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

182 if len(args) == 1: 

183 s = args[0] 

184 return pat.sub(repl, s) 

185 

186 else: 

187 fin, fout = args 

188 nn = 0 

189 for line in fin: 

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

191 nn += n 

192 fout.write(line) 

193 if nn == 2: 

194 break 

195 

196 if nn == 2: 

197 for line in fin: 

198 fout.write(line) 

199 

200 

201def escape_shell_arg(s): 

202 ''' 

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

204 insecure. 

205 ''' 

206 

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

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

209 else: 

210 return s 

211 

212 

213def escape_shell_args(args): 

214 ''' 

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

216 insecure. 

217 ''' 

218 

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

220 

221 

222golden_ratio = 1.61803 

223 

224# units in points 

225_units = { 

226 'i': 72., 

227 'c': 72./2.54, 

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

229 'p': 1.} 

230 

231inch = _units['i'] 

232cm = _units['c'] 

233 

234# some awsome colors 

235tango_colors = { 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

263} 

264 

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

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

267 'butter2')] 

268 

269 

270def color(x=None): 

271 ''' 

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

273 

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

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

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

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

278 transformed into the string form which GMT expects. 

279 ''' 

280 

281 if x is None: 

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

283 

284 if isinstance(x, int): 

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

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

287 else: 

288 return '0/0/0' 

289 

290 elif isinstance(x, str): 

291 if x in tango_colors: 

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

293 else: 

294 return x 

295 

296 return '%i/%i/%i' % x 

297 

298 

299def color_tup(x=None): 

300 if x is None: 

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

302 

303 if isinstance(x, int): 

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

305 return graph_colors[x] 

306 else: 

307 return (0, 0, 0) 

308 

309 elif isinstance(x, str): 

310 if x in tango_colors: 

311 return tango_colors[x] 

312 

313 return x 

314 

315 

316_gmt_installations = {} 

317 

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

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

320 

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

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

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

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

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

326# 'bin': '/usr/bin' } 

327 

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

329 

330 

331def key_version(a): 

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

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

334 

335 

336def newest_installed_gmt_version(): 

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

338 

339 

340def all_installed_gmt_versions(): 

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

342 

343 

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

345# changed. 

346 

347_gmt_defaults_by_version = {} 

348_gmt_defaults_by_version['4.2.1'] = r''' 

349# 

350# GMT-SYSTEM 4.2.1 Defaults file 

351# 

352#-------- Plot Media Parameters ------------- 

353PAGE_COLOR = 255/255/255 

354PAGE_ORIENTATION = portrait 

355PAPER_MEDIA = a4+ 

356#-------- Basemap Annotation Parameters ------ 

357ANNOT_MIN_ANGLE = 20 

358ANNOT_MIN_SPACING = 0 

359ANNOT_FONT_PRIMARY = Helvetica 

360ANNOT_FONT_SIZE = 12p 

361ANNOT_OFFSET_PRIMARY = 0.075i 

362ANNOT_FONT_SECONDARY = Helvetica 

363ANNOT_FONT_SIZE_SECONDARY = 16p 

364ANNOT_OFFSET_SECONDARY = 0.075i 

365DEGREE_SYMBOL = ring 

366HEADER_FONT = Helvetica 

367HEADER_FONT_SIZE = 36p 

368HEADER_OFFSET = 0.1875i 

369LABEL_FONT = Helvetica 

370LABEL_FONT_SIZE = 14p 

371LABEL_OFFSET = 0.1125i 

372OBLIQUE_ANNOTATION = 1 

373PLOT_CLOCK_FORMAT = hh:mm:ss 

374PLOT_DATE_FORMAT = yyyy-mm-dd 

375PLOT_DEGREE_FORMAT = +ddd:mm:ss 

376Y_AXIS_TYPE = hor_text 

377#-------- Basemap Layout Parameters --------- 

378BASEMAP_AXES = WESN 

379BASEMAP_FRAME_RGB = 0/0/0 

380BASEMAP_TYPE = plain 

381FRAME_PEN = 1.25p 

382FRAME_WIDTH = 0.075i 

383GRID_CROSS_SIZE_PRIMARY = 0i 

384GRID_CROSS_SIZE_SECONDARY = 0i 

385GRID_PEN_PRIMARY = 0.25p 

386GRID_PEN_SECONDARY = 0.5p 

387MAP_SCALE_HEIGHT = 0.075i 

388TICK_LENGTH = 0.075i 

389POLAR_CAP = 85/90 

390TICK_PEN = 0.5p 

391X_AXIS_LENGTH = 9i 

392Y_AXIS_LENGTH = 6i 

393X_ORIGIN = 1i 

394Y_ORIGIN = 1i 

395UNIX_TIME = FALSE 

396UNIX_TIME_POS = -0.75i/-0.75i 

397#-------- Color System Parameters ----------- 

398COLOR_BACKGROUND = 0/0/0 

399COLOR_FOREGROUND = 255/255/255 

400COLOR_NAN = 128/128/128 

401COLOR_IMAGE = adobe 

402COLOR_MODEL = rgb 

403HSV_MIN_SATURATION = 1 

404HSV_MAX_SATURATION = 0.1 

405HSV_MIN_VALUE = 0.3 

406HSV_MAX_VALUE = 1 

407#-------- PostScript Parameters ------------- 

408CHAR_ENCODING = ISOLatin1+ 

409DOTS_PR_INCH = 300 

410N_COPIES = 1 

411PS_COLOR = rgb 

412PS_IMAGE_COMPRESS = none 

413PS_IMAGE_FORMAT = ascii 

414PS_LINE_CAP = round 

415PS_LINE_JOIN = miter 

416PS_MITER_LIMIT = 35 

417PS_VERBOSE = FALSE 

418GLOBAL_X_SCALE = 1 

419GLOBAL_Y_SCALE = 1 

420#-------- I/O Format Parameters ------------- 

421D_FORMAT = %lg 

422FIELD_DELIMITER = tab 

423GRIDFILE_SHORTHAND = FALSE 

424GRID_FORMAT = nf 

425INPUT_CLOCK_FORMAT = hh:mm:ss 

426INPUT_DATE_FORMAT = yyyy-mm-dd 

427IO_HEADER = FALSE 

428N_HEADER_RECS = 1 

429OUTPUT_CLOCK_FORMAT = hh:mm:ss 

430OUTPUT_DATE_FORMAT = yyyy-mm-dd 

431OUTPUT_DEGREE_FORMAT = +D 

432XY_TOGGLE = FALSE 

433#-------- Projection Parameters ------------- 

434ELLIPSOID = WGS-84 

435MAP_SCALE_FACTOR = default 

436MEASURE_UNIT = inch 

437#-------- Calendar/Time Parameters ---------- 

438TIME_FORMAT_PRIMARY = full 

439TIME_FORMAT_SECONDARY = full 

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

441TIME_IS_INTERVAL = OFF 

442TIME_INTERVAL_FRACTION = 0.5 

443TIME_LANGUAGE = us 

444TIME_SYSTEM = other 

445TIME_UNIT = d 

446TIME_WEEK_START = Sunday 

447Y2K_OFFSET_YEAR = 1950 

448#-------- Miscellaneous Parameters ---------- 

449HISTORY = TRUE 

450INTERPOLANT = akima 

451LINE_STEP = 0.01i 

452VECTOR_SHAPE = 0 

453VERBOSE = FALSE''' 

454 

455_gmt_defaults_by_version['4.3.0'] = r''' 

456# 

457# GMT-SYSTEM 4.3.0 Defaults file 

458# 

459#-------- Plot Media Parameters ------------- 

460PAGE_COLOR = 255/255/255 

461PAGE_ORIENTATION = portrait 

462PAPER_MEDIA = a4+ 

463#-------- Basemap Annotation Parameters ------ 

464ANNOT_MIN_ANGLE = 20 

465ANNOT_MIN_SPACING = 0 

466ANNOT_FONT_PRIMARY = Helvetica 

467ANNOT_FONT_SIZE_PRIMARY = 12p 

468ANNOT_OFFSET_PRIMARY = 0.075i 

469ANNOT_FONT_SECONDARY = Helvetica 

470ANNOT_FONT_SIZE_SECONDARY = 16p 

471ANNOT_OFFSET_SECONDARY = 0.075i 

472DEGREE_SYMBOL = ring 

473HEADER_FONT = Helvetica 

474HEADER_FONT_SIZE = 36p 

475HEADER_OFFSET = 0.1875i 

476LABEL_FONT = Helvetica 

477LABEL_FONT_SIZE = 14p 

478LABEL_OFFSET = 0.1125i 

479OBLIQUE_ANNOTATION = 1 

480PLOT_CLOCK_FORMAT = hh:mm:ss 

481PLOT_DATE_FORMAT = yyyy-mm-dd 

482PLOT_DEGREE_FORMAT = +ddd:mm:ss 

483Y_AXIS_TYPE = hor_text 

484#-------- Basemap Layout Parameters --------- 

485BASEMAP_AXES = WESN 

486BASEMAP_FRAME_RGB = 0/0/0 

487BASEMAP_TYPE = plain 

488FRAME_PEN = 1.25p 

489FRAME_WIDTH = 0.075i 

490GRID_CROSS_SIZE_PRIMARY = 0i 

491GRID_PEN_PRIMARY = 0.25p 

492GRID_CROSS_SIZE_SECONDARY = 0i 

493GRID_PEN_SECONDARY = 0.5p 

494MAP_SCALE_HEIGHT = 0.075i 

495POLAR_CAP = 85/90 

496TICK_LENGTH = 0.075i 

497TICK_PEN = 0.5p 

498X_AXIS_LENGTH = 9i 

499Y_AXIS_LENGTH = 6i 

500X_ORIGIN = 1i 

501Y_ORIGIN = 1i 

502UNIX_TIME = FALSE 

503UNIX_TIME_POS = BL/-0.75i/-0.75i 

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

505#-------- Color System Parameters ----------- 

506COLOR_BACKGROUND = 0/0/0 

507COLOR_FOREGROUND = 255/255/255 

508COLOR_NAN = 128/128/128 

509COLOR_IMAGE = adobe 

510COLOR_MODEL = rgb 

511HSV_MIN_SATURATION = 1 

512HSV_MAX_SATURATION = 0.1 

513HSV_MIN_VALUE = 0.3 

514HSV_MAX_VALUE = 1 

515#-------- PostScript Parameters ------------- 

516CHAR_ENCODING = ISOLatin1+ 

517DOTS_PR_INCH = 300 

518N_COPIES = 1 

519PS_COLOR = rgb 

520PS_IMAGE_COMPRESS = none 

521PS_IMAGE_FORMAT = ascii 

522PS_LINE_CAP = round 

523PS_LINE_JOIN = miter 

524PS_MITER_LIMIT = 35 

525PS_VERBOSE = FALSE 

526GLOBAL_X_SCALE = 1 

527GLOBAL_Y_SCALE = 1 

528#-------- I/O Format Parameters ------------- 

529D_FORMAT = %lg 

530FIELD_DELIMITER = tab 

531GRIDFILE_SHORTHAND = FALSE 

532GRID_FORMAT = nf 

533INPUT_CLOCK_FORMAT = hh:mm:ss 

534INPUT_DATE_FORMAT = yyyy-mm-dd 

535IO_HEADER = FALSE 

536N_HEADER_RECS = 1 

537OUTPUT_CLOCK_FORMAT = hh:mm:ss 

538OUTPUT_DATE_FORMAT = yyyy-mm-dd 

539OUTPUT_DEGREE_FORMAT = +D 

540XY_TOGGLE = FALSE 

541#-------- Projection Parameters ------------- 

542ELLIPSOID = WGS-84 

543MAP_SCALE_FACTOR = default 

544MEASURE_UNIT = inch 

545#-------- Calendar/Time Parameters ---------- 

546TIME_FORMAT_PRIMARY = full 

547TIME_FORMAT_SECONDARY = full 

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

549TIME_IS_INTERVAL = OFF 

550TIME_INTERVAL_FRACTION = 0.5 

551TIME_LANGUAGE = us 

552TIME_UNIT = d 

553TIME_WEEK_START = Sunday 

554Y2K_OFFSET_YEAR = 1950 

555#-------- Miscellaneous Parameters ---------- 

556HISTORY = TRUE 

557INTERPOLANT = akima 

558LINE_STEP = 0.01i 

559VECTOR_SHAPE = 0 

560VERBOSE = FALSE''' 

561 

562 

563_gmt_defaults_by_version['4.3.1'] = r''' 

564# 

565# GMT-SYSTEM 4.3.1 Defaults file 

566# 

567#-------- Plot Media Parameters ------------- 

568PAGE_COLOR = 255/255/255 

569PAGE_ORIENTATION = portrait 

570PAPER_MEDIA = a4+ 

571#-------- Basemap Annotation Parameters ------ 

572ANNOT_MIN_ANGLE = 20 

573ANNOT_MIN_SPACING = 0 

574ANNOT_FONT_PRIMARY = Helvetica 

575ANNOT_FONT_SIZE_PRIMARY = 12p 

576ANNOT_OFFSET_PRIMARY = 0.075i 

577ANNOT_FONT_SECONDARY = Helvetica 

578ANNOT_FONT_SIZE_SECONDARY = 16p 

579ANNOT_OFFSET_SECONDARY = 0.075i 

580DEGREE_SYMBOL = ring 

581HEADER_FONT = Helvetica 

582HEADER_FONT_SIZE = 36p 

583HEADER_OFFSET = 0.1875i 

584LABEL_FONT = Helvetica 

585LABEL_FONT_SIZE = 14p 

586LABEL_OFFSET = 0.1125i 

587OBLIQUE_ANNOTATION = 1 

588PLOT_CLOCK_FORMAT = hh:mm:ss 

589PLOT_DATE_FORMAT = yyyy-mm-dd 

590PLOT_DEGREE_FORMAT = +ddd:mm:ss 

591Y_AXIS_TYPE = hor_text 

592#-------- Basemap Layout Parameters --------- 

593BASEMAP_AXES = WESN 

594BASEMAP_FRAME_RGB = 0/0/0 

595BASEMAP_TYPE = plain 

596FRAME_PEN = 1.25p 

597FRAME_WIDTH = 0.075i 

598GRID_CROSS_SIZE_PRIMARY = 0i 

599GRID_PEN_PRIMARY = 0.25p 

600GRID_CROSS_SIZE_SECONDARY = 0i 

601GRID_PEN_SECONDARY = 0.5p 

602MAP_SCALE_HEIGHT = 0.075i 

603POLAR_CAP = 85/90 

604TICK_LENGTH = 0.075i 

605TICK_PEN = 0.5p 

606X_AXIS_LENGTH = 9i 

607Y_AXIS_LENGTH = 6i 

608X_ORIGIN = 1i 

609Y_ORIGIN = 1i 

610UNIX_TIME = FALSE 

611UNIX_TIME_POS = BL/-0.75i/-0.75i 

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

613#-------- Color System Parameters ----------- 

614COLOR_BACKGROUND = 0/0/0 

615COLOR_FOREGROUND = 255/255/255 

616COLOR_NAN = 128/128/128 

617COLOR_IMAGE = adobe 

618COLOR_MODEL = rgb 

619HSV_MIN_SATURATION = 1 

620HSV_MAX_SATURATION = 0.1 

621HSV_MIN_VALUE = 0.3 

622HSV_MAX_VALUE = 1 

623#-------- PostScript Parameters ------------- 

624CHAR_ENCODING = ISOLatin1+ 

625DOTS_PR_INCH = 300 

626N_COPIES = 1 

627PS_COLOR = rgb 

628PS_IMAGE_COMPRESS = none 

629PS_IMAGE_FORMAT = ascii 

630PS_LINE_CAP = round 

631PS_LINE_JOIN = miter 

632PS_MITER_LIMIT = 35 

633PS_VERBOSE = FALSE 

634GLOBAL_X_SCALE = 1 

635GLOBAL_Y_SCALE = 1 

636#-------- I/O Format Parameters ------------- 

637D_FORMAT = %lg 

638FIELD_DELIMITER = tab 

639GRIDFILE_SHORTHAND = FALSE 

640GRID_FORMAT = nf 

641INPUT_CLOCK_FORMAT = hh:mm:ss 

642INPUT_DATE_FORMAT = yyyy-mm-dd 

643IO_HEADER = FALSE 

644N_HEADER_RECS = 1 

645OUTPUT_CLOCK_FORMAT = hh:mm:ss 

646OUTPUT_DATE_FORMAT = yyyy-mm-dd 

647OUTPUT_DEGREE_FORMAT = +D 

648XY_TOGGLE = FALSE 

649#-------- Projection Parameters ------------- 

650ELLIPSOID = WGS-84 

651MAP_SCALE_FACTOR = default 

652MEASURE_UNIT = inch 

653#-------- Calendar/Time Parameters ---------- 

654TIME_FORMAT_PRIMARY = full 

655TIME_FORMAT_SECONDARY = full 

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

657TIME_IS_INTERVAL = OFF 

658TIME_INTERVAL_FRACTION = 0.5 

659TIME_LANGUAGE = us 

660TIME_UNIT = d 

661TIME_WEEK_START = Sunday 

662Y2K_OFFSET_YEAR = 1950 

663#-------- Miscellaneous Parameters ---------- 

664HISTORY = TRUE 

665INTERPOLANT = akima 

666LINE_STEP = 0.01i 

667VECTOR_SHAPE = 0 

668VERBOSE = FALSE''' 

669 

670 

671_gmt_defaults_by_version['4.4.0'] = r''' 

672# 

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

674# 

675#-------- Plot Media Parameters ------------- 

676PAGE_COLOR = 255/255/255 

677PAGE_ORIENTATION = portrait 

678PAPER_MEDIA = a4+ 

679#-------- Basemap Annotation Parameters ------ 

680ANNOT_MIN_ANGLE = 20 

681ANNOT_MIN_SPACING = 0 

682ANNOT_FONT_PRIMARY = Helvetica 

683ANNOT_FONT_SIZE_PRIMARY = 14p 

684ANNOT_OFFSET_PRIMARY = 0.075i 

685ANNOT_FONT_SECONDARY = Helvetica 

686ANNOT_FONT_SIZE_SECONDARY = 16p 

687ANNOT_OFFSET_SECONDARY = 0.075i 

688DEGREE_SYMBOL = ring 

689HEADER_FONT = Helvetica 

690HEADER_FONT_SIZE = 36p 

691HEADER_OFFSET = 0.1875i 

692LABEL_FONT = Helvetica 

693LABEL_FONT_SIZE = 14p 

694LABEL_OFFSET = 0.1125i 

695OBLIQUE_ANNOTATION = 1 

696PLOT_CLOCK_FORMAT = hh:mm:ss 

697PLOT_DATE_FORMAT = yyyy-mm-dd 

698PLOT_DEGREE_FORMAT = +ddd:mm:ss 

699Y_AXIS_TYPE = hor_text 

700#-------- Basemap Layout Parameters --------- 

701BASEMAP_AXES = WESN 

702BASEMAP_FRAME_RGB = 0/0/0 

703BASEMAP_TYPE = plain 

704FRAME_PEN = 1.25p 

705FRAME_WIDTH = 0.075i 

706GRID_CROSS_SIZE_PRIMARY = 0i 

707GRID_PEN_PRIMARY = 0.25p 

708GRID_CROSS_SIZE_SECONDARY = 0i 

709GRID_PEN_SECONDARY = 0.5p 

710MAP_SCALE_HEIGHT = 0.075i 

711POLAR_CAP = 85/90 

712TICK_LENGTH = 0.075i 

713TICK_PEN = 0.5p 

714X_AXIS_LENGTH = 9i 

715Y_AXIS_LENGTH = 6i 

716X_ORIGIN = 1i 

717Y_ORIGIN = 1i 

718UNIX_TIME = FALSE 

719UNIX_TIME_POS = BL/-0.75i/-0.75i 

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

721#-------- Color System Parameters ----------- 

722COLOR_BACKGROUND = 0/0/0 

723COLOR_FOREGROUND = 255/255/255 

724COLOR_NAN = 128/128/128 

725COLOR_IMAGE = adobe 

726COLOR_MODEL = rgb 

727HSV_MIN_SATURATION = 1 

728HSV_MAX_SATURATION = 0.1 

729HSV_MIN_VALUE = 0.3 

730HSV_MAX_VALUE = 1 

731#-------- PostScript Parameters ------------- 

732CHAR_ENCODING = ISOLatin1+ 

733DOTS_PR_INCH = 300 

734N_COPIES = 1 

735PS_COLOR = rgb 

736PS_IMAGE_COMPRESS = lzw 

737PS_IMAGE_FORMAT = ascii 

738PS_LINE_CAP = round 

739PS_LINE_JOIN = miter 

740PS_MITER_LIMIT = 35 

741PS_VERBOSE = FALSE 

742GLOBAL_X_SCALE = 1 

743GLOBAL_Y_SCALE = 1 

744#-------- I/O Format Parameters ------------- 

745D_FORMAT = %lg 

746FIELD_DELIMITER = tab 

747GRIDFILE_SHORTHAND = FALSE 

748GRID_FORMAT = nf 

749INPUT_CLOCK_FORMAT = hh:mm:ss 

750INPUT_DATE_FORMAT = yyyy-mm-dd 

751IO_HEADER = FALSE 

752N_HEADER_RECS = 1 

753OUTPUT_CLOCK_FORMAT = hh:mm:ss 

754OUTPUT_DATE_FORMAT = yyyy-mm-dd 

755OUTPUT_DEGREE_FORMAT = +D 

756XY_TOGGLE = FALSE 

757#-------- Projection Parameters ------------- 

758ELLIPSOID = WGS-84 

759MAP_SCALE_FACTOR = default 

760MEASURE_UNIT = inch 

761#-------- Calendar/Time Parameters ---------- 

762TIME_FORMAT_PRIMARY = full 

763TIME_FORMAT_SECONDARY = full 

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

765TIME_IS_INTERVAL = OFF 

766TIME_INTERVAL_FRACTION = 0.5 

767TIME_LANGUAGE = us 

768TIME_UNIT = d 

769TIME_WEEK_START = Sunday 

770Y2K_OFFSET_YEAR = 1950 

771#-------- Miscellaneous Parameters ---------- 

772HISTORY = TRUE 

773INTERPOLANT = akima 

774LINE_STEP = 0.01i 

775VECTOR_SHAPE = 0 

776VERBOSE = FALSE 

777''' 

778 

779_gmt_defaults_by_version['4.5.2'] = r''' 

780# 

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

782# 

783#-------- Plot Media Parameters ------------- 

784PAGE_COLOR = white 

785PAGE_ORIENTATION = portrait 

786PAPER_MEDIA = a4+ 

787#-------- Basemap Annotation Parameters ------ 

788ANNOT_MIN_ANGLE = 20 

789ANNOT_MIN_SPACING = 0 

790ANNOT_FONT_PRIMARY = Helvetica 

791ANNOT_FONT_SIZE_PRIMARY = 14p 

792ANNOT_OFFSET_PRIMARY = 0.075i 

793ANNOT_FONT_SECONDARY = Helvetica 

794ANNOT_FONT_SIZE_SECONDARY = 16p 

795ANNOT_OFFSET_SECONDARY = 0.075i 

796DEGREE_SYMBOL = ring 

797HEADER_FONT = Helvetica 

798HEADER_FONT_SIZE = 36p 

799HEADER_OFFSET = 0.1875i 

800LABEL_FONT = Helvetica 

801LABEL_FONT_SIZE = 14p 

802LABEL_OFFSET = 0.1125i 

803OBLIQUE_ANNOTATION = 1 

804PLOT_CLOCK_FORMAT = hh:mm:ss 

805PLOT_DATE_FORMAT = yyyy-mm-dd 

806PLOT_DEGREE_FORMAT = +ddd:mm:ss 

807Y_AXIS_TYPE = hor_text 

808#-------- Basemap Layout Parameters --------- 

809BASEMAP_AXES = WESN 

810BASEMAP_FRAME_RGB = black 

811BASEMAP_TYPE = plain 

812FRAME_PEN = 1.25p 

813FRAME_WIDTH = 0.075i 

814GRID_CROSS_SIZE_PRIMARY = 0i 

815GRID_PEN_PRIMARY = 0.25p 

816GRID_CROSS_SIZE_SECONDARY = 0i 

817GRID_PEN_SECONDARY = 0.5p 

818MAP_SCALE_HEIGHT = 0.075i 

819POLAR_CAP = 85/90 

820TICK_LENGTH = 0.075i 

821TICK_PEN = 0.5p 

822X_AXIS_LENGTH = 9i 

823Y_AXIS_LENGTH = 6i 

824X_ORIGIN = 1i 

825Y_ORIGIN = 1i 

826UNIX_TIME = FALSE 

827UNIX_TIME_POS = BL/-0.75i/-0.75i 

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

829#-------- Color System Parameters ----------- 

830COLOR_BACKGROUND = black 

831COLOR_FOREGROUND = white 

832COLOR_NAN = 128 

833COLOR_IMAGE = adobe 

834COLOR_MODEL = rgb 

835HSV_MIN_SATURATION = 1 

836HSV_MAX_SATURATION = 0.1 

837HSV_MIN_VALUE = 0.3 

838HSV_MAX_VALUE = 1 

839#-------- PostScript Parameters ------------- 

840CHAR_ENCODING = ISOLatin1+ 

841DOTS_PR_INCH = 300 

842GLOBAL_X_SCALE = 1 

843GLOBAL_Y_SCALE = 1 

844N_COPIES = 1 

845PS_COLOR = rgb 

846PS_IMAGE_COMPRESS = lzw 

847PS_IMAGE_FORMAT = ascii 

848PS_LINE_CAP = round 

849PS_LINE_JOIN = miter 

850PS_MITER_LIMIT = 35 

851PS_VERBOSE = FALSE 

852TRANSPARENCY = 0 

853#-------- I/O Format Parameters ------------- 

854D_FORMAT = %.12lg 

855FIELD_DELIMITER = tab 

856GRIDFILE_FORMAT = nf 

857GRIDFILE_SHORTHAND = FALSE 

858INPUT_CLOCK_FORMAT = hh:mm:ss 

859INPUT_DATE_FORMAT = yyyy-mm-dd 

860IO_HEADER = FALSE 

861N_HEADER_RECS = 1 

862NAN_RECORDS = pass 

863OUTPUT_CLOCK_FORMAT = hh:mm:ss 

864OUTPUT_DATE_FORMAT = yyyy-mm-dd 

865OUTPUT_DEGREE_FORMAT = D 

866XY_TOGGLE = FALSE 

867#-------- Projection Parameters ------------- 

868ELLIPSOID = WGS-84 

869MAP_SCALE_FACTOR = default 

870MEASURE_UNIT = inch 

871#-------- Calendar/Time Parameters ---------- 

872TIME_FORMAT_PRIMARY = full 

873TIME_FORMAT_SECONDARY = full 

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

875TIME_IS_INTERVAL = OFF 

876TIME_INTERVAL_FRACTION = 0.5 

877TIME_LANGUAGE = us 

878TIME_UNIT = d 

879TIME_WEEK_START = Sunday 

880Y2K_OFFSET_YEAR = 1950 

881#-------- Miscellaneous Parameters ---------- 

882HISTORY = TRUE 

883INTERPOLANT = akima 

884LINE_STEP = 0.01i 

885VECTOR_SHAPE = 0 

886VERBOSE = FALSE 

887''' 

888 

889_gmt_defaults_by_version['4.5.3'] = r''' 

890# 

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

892# 

893#-------- Plot Media Parameters ------------- 

894PAGE_COLOR = white 

895PAGE_ORIENTATION = portrait 

896PAPER_MEDIA = a4+ 

897#-------- Basemap Annotation Parameters ------ 

898ANNOT_MIN_ANGLE = 20 

899ANNOT_MIN_SPACING = 0 

900ANNOT_FONT_PRIMARY = Helvetica 

901ANNOT_FONT_SIZE_PRIMARY = 14p 

902ANNOT_OFFSET_PRIMARY = 0.075i 

903ANNOT_FONT_SECONDARY = Helvetica 

904ANNOT_FONT_SIZE_SECONDARY = 16p 

905ANNOT_OFFSET_SECONDARY = 0.075i 

906DEGREE_SYMBOL = ring 

907HEADER_FONT = Helvetica 

908HEADER_FONT_SIZE = 36p 

909HEADER_OFFSET = 0.1875i 

910LABEL_FONT = Helvetica 

911LABEL_FONT_SIZE = 14p 

912LABEL_OFFSET = 0.1125i 

913OBLIQUE_ANNOTATION = 1 

914PLOT_CLOCK_FORMAT = hh:mm:ss 

915PLOT_DATE_FORMAT = yyyy-mm-dd 

916PLOT_DEGREE_FORMAT = +ddd:mm:ss 

917Y_AXIS_TYPE = hor_text 

918#-------- Basemap Layout Parameters --------- 

919BASEMAP_AXES = WESN 

920BASEMAP_FRAME_RGB = black 

921BASEMAP_TYPE = plain 

922FRAME_PEN = 1.25p 

923FRAME_WIDTH = 0.075i 

924GRID_CROSS_SIZE_PRIMARY = 0i 

925GRID_PEN_PRIMARY = 0.25p 

926GRID_CROSS_SIZE_SECONDARY = 0i 

927GRID_PEN_SECONDARY = 0.5p 

928MAP_SCALE_HEIGHT = 0.075i 

929POLAR_CAP = 85/90 

930TICK_LENGTH = 0.075i 

931TICK_PEN = 0.5p 

932X_AXIS_LENGTH = 9i 

933Y_AXIS_LENGTH = 6i 

934X_ORIGIN = 1i 

935Y_ORIGIN = 1i 

936UNIX_TIME = FALSE 

937UNIX_TIME_POS = BL/-0.75i/-0.75i 

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

939#-------- Color System Parameters ----------- 

940COLOR_BACKGROUND = black 

941COLOR_FOREGROUND = white 

942COLOR_NAN = 128 

943COLOR_IMAGE = adobe 

944COLOR_MODEL = rgb 

945HSV_MIN_SATURATION = 1 

946HSV_MAX_SATURATION = 0.1 

947HSV_MIN_VALUE = 0.3 

948HSV_MAX_VALUE = 1 

949#-------- PostScript Parameters ------------- 

950CHAR_ENCODING = ISOLatin1+ 

951DOTS_PR_INCH = 300 

952GLOBAL_X_SCALE = 1 

953GLOBAL_Y_SCALE = 1 

954N_COPIES = 1 

955PS_COLOR = rgb 

956PS_IMAGE_COMPRESS = lzw 

957PS_IMAGE_FORMAT = ascii 

958PS_LINE_CAP = round 

959PS_LINE_JOIN = miter 

960PS_MITER_LIMIT = 35 

961PS_VERBOSE = FALSE 

962TRANSPARENCY = 0 

963#-------- I/O Format Parameters ------------- 

964D_FORMAT = %.12lg 

965FIELD_DELIMITER = tab 

966GRIDFILE_FORMAT = nf 

967GRIDFILE_SHORTHAND = FALSE 

968INPUT_CLOCK_FORMAT = hh:mm:ss 

969INPUT_DATE_FORMAT = yyyy-mm-dd 

970IO_HEADER = FALSE 

971N_HEADER_RECS = 1 

972NAN_RECORDS = pass 

973OUTPUT_CLOCK_FORMAT = hh:mm:ss 

974OUTPUT_DATE_FORMAT = yyyy-mm-dd 

975OUTPUT_DEGREE_FORMAT = D 

976XY_TOGGLE = FALSE 

977#-------- Projection Parameters ------------- 

978ELLIPSOID = WGS-84 

979MAP_SCALE_FACTOR = default 

980MEASURE_UNIT = inch 

981#-------- Calendar/Time Parameters ---------- 

982TIME_FORMAT_PRIMARY = full 

983TIME_FORMAT_SECONDARY = full 

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

985TIME_IS_INTERVAL = OFF 

986TIME_INTERVAL_FRACTION = 0.5 

987TIME_LANGUAGE = us 

988TIME_UNIT = d 

989TIME_WEEK_START = Sunday 

990Y2K_OFFSET_YEAR = 1950 

991#-------- Miscellaneous Parameters ---------- 

992HISTORY = TRUE 

993INTERPOLANT = akima 

994LINE_STEP = 0.01i 

995VECTOR_SHAPE = 0 

996VERBOSE = FALSE 

997''' 

998 

999_gmt_defaults_by_version['5.1.2'] = r''' 

1000# 

1001# GMT 5.1.2 Defaults file 

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

1003# $Revision: 13836 $ 

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

1005# 

1006# COLOR Parameters 

1007# 

1008COLOR_BACKGROUND = black 

1009COLOR_FOREGROUND = white 

1010COLOR_NAN = 127.5 

1011COLOR_MODEL = none 

1012COLOR_HSV_MIN_S = 1 

1013COLOR_HSV_MAX_S = 0.1 

1014COLOR_HSV_MIN_V = 0.3 

1015COLOR_HSV_MAX_V = 1 

1016# 

1017# DIR Parameters 

1018# 

1019DIR_DATA = 

1020DIR_DCW = 

1021DIR_GSHHG = 

1022# 

1023# FONT Parameters 

1024# 

1025FONT_ANNOT_PRIMARY = 14p,Helvetica,black 

1026FONT_ANNOT_SECONDARY = 16p,Helvetica,black 

1027FONT_LABEL = 14p,Helvetica,black 

1028FONT_LOGO = 8p,Helvetica,black 

1029FONT_TITLE = 24p,Helvetica,black 

1030# 

1031# FORMAT Parameters 

1032# 

1033FORMAT_CLOCK_IN = hh:mm:ss 

1034FORMAT_CLOCK_OUT = hh:mm:ss 

1035FORMAT_CLOCK_MAP = hh:mm:ss 

1036FORMAT_DATE_IN = yyyy-mm-dd 

1037FORMAT_DATE_OUT = yyyy-mm-dd 

1038FORMAT_DATE_MAP = yyyy-mm-dd 

1039FORMAT_GEO_OUT = D 

1040FORMAT_GEO_MAP = ddd:mm:ss 

1041FORMAT_FLOAT_OUT = %.12g 

1042FORMAT_FLOAT_MAP = %.12g 

1043FORMAT_TIME_PRIMARY_MAP = full 

1044FORMAT_TIME_SECONDARY_MAP = full 

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

1046# 

1047# GMT Miscellaneous Parameters 

1048# 

1049GMT_COMPATIBILITY = 4 

1050GMT_CUSTOM_LIBS = 

1051GMT_EXTRAPOLATE_VAL = NaN 

1052GMT_FFT = auto 

1053GMT_HISTORY = true 

1054GMT_INTERPOLANT = akima 

1055GMT_TRIANGULATE = Shewchuk 

1056GMT_VERBOSE = compat 

1057GMT_LANGUAGE = us 

1058# 

1059# I/O Parameters 

1060# 

1061IO_COL_SEPARATOR = tab 

1062IO_GRIDFILE_FORMAT = nf 

1063IO_GRIDFILE_SHORTHAND = false 

1064IO_HEADER = false 

1065IO_N_HEADER_RECS = 0 

1066IO_NAN_RECORDS = pass 

1067IO_NC4_CHUNK_SIZE = auto 

1068IO_NC4_DEFLATION_LEVEL = 3 

1069IO_LONLAT_TOGGLE = false 

1070IO_SEGMENT_MARKER = > 

1071# 

1072# MAP Parameters 

1073# 

1074MAP_ANNOT_MIN_ANGLE = 20 

1075MAP_ANNOT_MIN_SPACING = 0p 

1076MAP_ANNOT_OBLIQUE = 1 

1077MAP_ANNOT_OFFSET_PRIMARY = 0.075i 

1078MAP_ANNOT_OFFSET_SECONDARY = 0.075i 

1079MAP_ANNOT_ORTHO = we 

1080MAP_DEFAULT_PEN = default,black 

1081MAP_DEGREE_SYMBOL = ring 

1082MAP_FRAME_AXES = WESNZ 

1083MAP_FRAME_PEN = thicker,black 

1084MAP_FRAME_TYPE = fancy 

1085MAP_FRAME_WIDTH = 5p 

1086MAP_GRID_CROSS_SIZE_PRIMARY = 0p 

1087MAP_GRID_CROSS_SIZE_SECONDARY = 0p 

1088MAP_GRID_PEN_PRIMARY = default,black 

1089MAP_GRID_PEN_SECONDARY = thinner,black 

1090MAP_LABEL_OFFSET = 0.1944i 

1091MAP_LINE_STEP = 0.75p 

1092MAP_LOGO = false 

1093MAP_LOGO_POS = BL/-54p/-54p 

1094MAP_ORIGIN_X = 1i 

1095MAP_ORIGIN_Y = 1i 

1096MAP_POLAR_CAP = 85/90 

1097MAP_SCALE_HEIGHT = 5p 

1098MAP_TICK_LENGTH_PRIMARY = 5p/2.5p 

1099MAP_TICK_LENGTH_SECONDARY = 15p/3.75p 

1100MAP_TICK_PEN_PRIMARY = thinner,black 

1101MAP_TICK_PEN_SECONDARY = thinner,black 

1102MAP_TITLE_OFFSET = 14p 

1103MAP_VECTOR_SHAPE = 0 

1104# 

1105# Projection Parameters 

1106# 

1107PROJ_AUX_LATITUDE = authalic 

1108PROJ_ELLIPSOID = WGS-84 

1109PROJ_LENGTH_UNIT = cm 

1110PROJ_MEAN_RADIUS = authalic 

1111PROJ_SCALE_FACTOR = default 

1112# 

1113# PostScript Parameters 

1114# 

1115PS_CHAR_ENCODING = ISOLatin1+ 

1116PS_COLOR_MODEL = rgb 

1117PS_COMMENTS = false 

1118PS_IMAGE_COMPRESS = deflate,5 

1119PS_LINE_CAP = butt 

1120PS_LINE_JOIN = miter 

1121PS_MITER_LIMIT = 35 

1122PS_MEDIA = a4 

1123PS_PAGE_COLOR = white 

1124PS_PAGE_ORIENTATION = portrait 

1125PS_SCALE_X = 1 

1126PS_SCALE_Y = 1 

1127PS_TRANSPARENCY = Normal 

1128# 

1129# Calendar/Time Parameters 

1130# 

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

1132TIME_IS_INTERVAL = off 

1133TIME_INTERVAL_FRACTION = 0.5 

1134TIME_UNIT = s 

1135TIME_WEEK_START = Monday 

1136TIME_Y2K_OFFSET_YEAR = 1950 

1137''' 

1138 

1139 

1140def get_gmt_version(gmtdefaultsbinary, gmthomedir=None): 

1141 args = [gmtdefaultsbinary] 

1142 

1143 environ = os.environ.copy() 

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

1145 

1146 p = subprocess.Popen( 

1147 args, 

1148 stdout=subprocess.PIPE, 

1149 stderr=subprocess.PIPE, 

1150 env=environ) 

1151 

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

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

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

1155 

1156 if not m: 

1157 raise GMTInstallationProblem( 

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

1159 % gmtdefaultsbinary) 

1160 

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

1162 

1163 

1164def detect_gmt_installations(): 

1165 

1166 installations = {} 

1167 errmesses = [] 

1168 

1169 # GMT 4.x: 

1170 try: 

1171 p = subprocess.Popen( 

1172 ['GMT'], 

1173 stdout=subprocess.PIPE, 

1174 stderr=subprocess.PIPE) 

1175 

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

1177 

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

1179 if not m: 

1180 raise GMTInstallationProblem( 

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

1182 

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

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

1185 

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

1187 if not m: 

1188 raise GMTInstallationProblem( 

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

1190 

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

1192 

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

1194 if not m: 

1195 raise GMTInstallationProblem( 

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

1197 

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

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

1200 raise GMTInstallationProblem( 

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

1202 

1203 gmthome = gmtshare[:-6] 

1204 

1205 installations[version] = { 

1206 'home': gmthome, 

1207 'bin': gmtbin} 

1208 

1209 except OSError as e: 

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

1211 

1212 try: 

1213 version = str(subprocess.check_output( 

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

1215 gmtbin = str(subprocess.check_output( 

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

1217 installations[version] = { 

1218 'bin': gmtbin} 

1219 

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

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

1222 

1223 if not installations: 

1224 s = [] 

1225 for (progname, errmess) in errmesses: 

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

1227 

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

1229 

1230 return installations 

1231 

1232 

1233def appropriate_defaults_version(version): 

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

1235 for iavail, avail in enumerate(avails): 

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

1237 return version 

1238 

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

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

1241 

1242 return avails[-1] 

1243 

1244 

1245def gmt_default_config(version): 

1246 ''' 

1247 Get default GMT configuration dict for given version. 

1248 ''' 

1249 

1250 xversion = appropriate_defaults_version(version) 

1251 

1252 # if not version in _gmt_defaults_by_version: 

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

1254 

1255 gmt_defaults = _gmt_defaults_by_version[xversion] 

1256 

1257 d = {} 

1258 for line in gmt_defaults.splitlines(): 

1259 sline = line.strip() 

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

1261 continue 

1262 

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

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

1265 

1266 return d 

1267 

1268 

1269def diff_defaults(v1, v2): 

1270 d1 = gmt_default_config(v1) 

1271 d2 = gmt_default_config(v2) 

1272 for k in d1: 

1273 if k not in d2: 

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

1275 else: 

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

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

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

1279 

1280 for k in d2: 

1281 if k not in d1: 

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

1283 

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

1285 

1286 

1287def check_gmt_installation(installation): 

1288 

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

1290 bin_dir = installation['bin'] 

1291 version = installation['version'] 

1292 

1293 for d in home_dir, bin_dir: 

1294 if d is not None: 

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

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

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

1298 

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

1300 

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

1302 gmtdefaults = pjoin(bin_dir, 'gmtdefaults') 

1303 

1304 versionfound = get_gmt_version(gmtdefaults, home_dir) 

1305 

1306 if versionfound != version: 

1307 raise GMTInstallationProblem(( 

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

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

1310 version, versionfound, gmtdefaults)) 

1311 

1312 

1313def get_gmt_installation(version): 

1314 setup_gmt_installations() 

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

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

1317 % (version, newest_installed_gmt_version())) 

1318 

1319 version = 'newest' 

1320 

1321 if version == 'newest': 

1322 version = newest_installed_gmt_version() 

1323 

1324 installation = dict(_gmt_installations[version]) 

1325 

1326 return installation 

1327 

1328 

1329def setup_gmt_installations(): 

1330 if not setup_gmt_installations.have_done: 

1331 if not _gmt_installations: 

1332 

1333 _gmt_installations.update(detect_gmt_installations()) 

1334 

1335 # store defaults as dicts into the gmt installations dicts 

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

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

1338 installation['version'] = version 

1339 

1340 for installation in _gmt_installations.values(): 

1341 check_gmt_installation(installation) 

1342 

1343 setup_gmt_installations.have_done = True 

1344 

1345 

1346setup_gmt_installations.have_done = False 

1347 

1348_paper_sizes_a = '''A0 2380 3368 

1349 A1 1684 2380 

1350 A2 1190 1684 

1351 A3 842 1190 

1352 A4 595 842 

1353 A5 421 595 

1354 A6 297 421 

1355 A7 210 297 

1356 A8 148 210 

1357 A9 105 148 

1358 A10 74 105 

1359 B0 2836 4008 

1360 B1 2004 2836 

1361 B2 1418 2004 

1362 B3 1002 1418 

1363 B4 709 1002 

1364 B5 501 709 

1365 archA 648 864 

1366 archB 864 1296 

1367 archC 1296 1728 

1368 archD 1728 2592 

1369 archE 2592 3456 

1370 flsa 612 936 

1371 halfletter 396 612 

1372 note 540 720 

1373 letter 612 792 

1374 legal 612 1008 

1375 11x17 792 1224 

1376 ledger 1224 792''' 

1377 

1378 

1379_paper_sizes = {} 

1380 

1381 

1382def setup_paper_sizes(): 

1383 if not _paper_sizes: 

1384 for line in _paper_sizes_a.splitlines(): 

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

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

1387 

1388 

1389def get_paper_size(k): 

1390 setup_paper_sizes() 

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

1392 

1393 

1394def all_paper_sizes(): 

1395 setup_paper_sizes() 

1396 return _paper_sizes 

1397 

1398 

1399def measure_unit(gmt_config): 

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

1401 if k in gmt_config: 

1402 return gmt_config[k] 

1403 

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

1405 

1406 

1407def paper_media(gmt_config): 

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

1409 if k in gmt_config: 

1410 return gmt_config[k] 

1411 

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

1413 

1414 

1415def page_orientation(gmt_config): 

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

1417 if k in gmt_config: 

1418 return gmt_config[k] 

1419 

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

1421 

1422 

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

1424 

1425 leftmargin, topmargin, rightmargin, bottommargin = margins 

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

1427 

1428 paper_size = get_paper_size(paper_media(gmt_config)) 

1429 if not portrait: 

1430 paper_size = paper_size[1], paper_size[0] 

1431 

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

1433 2.0 + leftmargin 

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

1435 2.0 + bottommargin 

1436 

1437 if portrait: 

1438 bb1 = int((xoffset - leftmargin)) 

1439 bb2 = int((yoffset - bottommargin)) 

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

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

1442 else: 

1443 bb1 = int((yoffset - topmargin)) 

1444 bb2 = int((xoffset - leftmargin)) 

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

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

1447 

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

1449 

1450 

1451def gmtdefaults_as_text(version='newest'): 

1452 

1453 ''' 

1454 Get the built-in gmtdefaults. 

1455 ''' 

1456 

1457 if version not in _gmt_installations: 

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

1459 % (version, newest_installed_gmt_version())) 

1460 version = 'newest' 

1461 

1462 if version == 'newest': 

1463 version = newest_installed_gmt_version() 

1464 

1465 return _gmt_defaults_by_version[version] 

1466 

1467 

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

1469 ''' 

1470 Write COARDS compliant netcdf (grd) file. 

1471 ''' 

1472 

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

1474 ny, nx = z.shape 

1475 nc = netcdf_file(filename, 'w') 

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

1477 

1478 if naming == 'xy': 

1479 kx, ky = 'x', 'y' 

1480 else: 

1481 kx, ky = 'lon', 'lat' 

1482 

1483 nc.node_offset = 0 

1484 if title is not None: 

1485 nc.title = title 

1486 

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

1488 nc.createDimension(kx, nx) 

1489 nc.createDimension(ky, ny) 

1490 

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

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

1493 if naming == 'xy': 

1494 xvar.long_name = kx 

1495 yvar.long_name = ky 

1496 else: 

1497 xvar.long_name = 'longitude' 

1498 xvar.units = 'degrees_east' 

1499 yvar.long_name = 'latitude' 

1500 yvar.units = 'degrees_north' 

1501 

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

1503 

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

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

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

1507 

1508 nc.close() 

1509 

1510 

1511def to_array(var): 

1512 arr = var[:].copy() 

1513 if hasattr(var, 'scale_factor'): 

1514 arr *= var.scale_factor 

1515 

1516 if hasattr(var, 'add_offset'): 

1517 arr += var.add_offset 

1518 

1519 return arr 

1520 

1521 

1522def loadgrd(filename): 

1523 ''' 

1524 Read COARDS compliant netcdf (grd) file. 

1525 ''' 

1526 

1527 nc = netcdf_file(filename, 'r') 

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

1529 kx = 'x' 

1530 ky = 'y' 

1531 if 'lon' in vkeys: 

1532 kx = 'lon' 

1533 if 'lat' in vkeys: 

1534 ky = 'lat' 

1535 

1536 kz = 'z' 

1537 if 'altitude' in vkeys: 

1538 kz = 'altitude' 

1539 

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

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

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

1543 

1544 nc.close() 

1545 return x, y, z 

1546 

1547 

1548def centers_to_edges(asorted): 

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

1550 

1551 

1552def nvals(asorted): 

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

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

1555 

1556 

1557def guess_vals(asorted): 

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

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

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

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

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

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

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

1565 

1566 

1567def blockmean(asorted, b): 

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

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

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

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

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

1573 return ( 

1574 asorted[indis[:-1]], 

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

1576 

1577 

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

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

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

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

1582 

1583 zindi = yindi*nx+xindi 

1584 order = num.argsort(zindi) 

1585 z = z[order] 

1586 zindi = zindi[order] 

1587 

1588 zindi, z = blockmean(zindi, z) 

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

1590 znew[:] = num.nan 

1591 znew[zindi] = z 

1592 return znew.reshape(ny, nx) 

1593 

1594 

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

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

1597 xs = x_sorted 

1598 ys = y_sorted 

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

1600 if mode == 'nonrandom': 

1601 return nxs, nys, 0 

1602 elif xs.size == nxs*nys: 

1603 # exact match 

1604 return nxs, nys, 0 

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

1606 # possibly randomly sampled 

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

1608 nys = nxs 

1609 return nxs, nys, 2 

1610 else: 

1611 return nxs, nys, 1 

1612 

1613 

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

1615 ''' 

1616 Grid tabular XYZ data by binning. 

1617 

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

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

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

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

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

1623 

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

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

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

1627 ''' 

1628 

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

1630 assert x.size == y.size == z.size 

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

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

1633 if badness <= 1: 

1634 xf = guess_vals(xs) 

1635 yf = guess_vals(ys) 

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

1637 else: 

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

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

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

1641 

1642 return xf, yf, zf 

1643 

1644 

1645def tabledata(xf, yf, zf): 

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

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

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

1649 z = zf.flatten() 

1650 return x, y, z 

1651 

1652 

1653def double1d(a): 

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

1655 a2[::2] = a 

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

1657 return a2 

1658 

1659 

1660def double2d(f): 

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

1662 f2[:, :] = num.nan 

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

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

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

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

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

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

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

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

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

1672 return f2 

1673 

1674 

1675def doublegrid(x, y, z): 

1676 x2 = double1d(x) 

1677 y2 = double1d(y) 

1678 z2 = double2d(z) 

1679 return x2, y2, z2 

1680 

1681 

1682class Guru(object): 

1683 ''' 

1684 Abstract base class providing template interpolation, accessible as 

1685 attributes. 

1686 

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

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

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

1690 with the templates. 

1691 ''' 

1692 

1693 def __init__(self): 

1694 self.templates = {} 

1695 

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

1697 params = self.get_params(**kwargs) 

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

1699 return strings 

1700 

1701 # hand through templates dict 

1702 def __getitem__(self, template_name): 

1703 return self.templates[template_name] 

1704 

1705 def __setitem__(self, template_name, template): 

1706 self.templates[template_name] = template 

1707 

1708 def __contains__(self, template_name): 

1709 return template_name in self.templates 

1710 

1711 def __iter__(self): 

1712 return iter(self.templates) 

1713 

1714 def __len__(self): 

1715 return len(self.templates) 

1716 

1717 def __delitem__(self, template_name): 

1718 del self.templates[template_name] 

1719 

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

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

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

1723 

1724 def __getattr__(self, template_names): 

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

1726 raise AttributeError(template_names) 

1727 

1728 def f(**kwargs): 

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

1730 

1731 return f 

1732 

1733 

1734class Ax(AutoScaler): 

1735 ''' 

1736 Ax description with autoscaling capabilities. 

1737 

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

1739 public attributes, plus the following additional attributes 

1740 (with default values given in paranthesis): 

1741 

1742 .. py:attribute:: label 

1743 

1744 Ax label (without unit). 

1745 

1746 .. py:attribute:: unit 

1747 

1748 Physical unit of the data attached to this ax. 

1749 

1750 .. py:attribute:: scaled_unit 

1751 

1752 (see below) 

1753 

1754 .. py:attribute:: scaled_unit_factor 

1755 

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

1757 

1758 unit = scaled_unit_factor x scaled_unit. 

1759 

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

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

1762 1e9.) 

1763 

1764 .. py:attribute:: limits 

1765 

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

1767 

1768 .. py:attribute:: masking 

1769 

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

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

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

1773 

1774 ''' 

1775 

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

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

1778 

1779 AutoScaler.__init__(self, **kwargs) 

1780 self.label = label 

1781 self.unit = unit 

1782 self.scaled_unit_factor = scaled_unit_factor 

1783 self.scaled_unit = scaled_unit 

1784 self.limits = limits 

1785 self.masking = masking 

1786 

1787 def label_str(self, exp, unit): 

1788 ''' 

1789 Get label string including the unit and multiplier. 

1790 ''' 

1791 

1792 slabel, sunit, sexp = '', '', '' 

1793 if self.label: 

1794 slabel = self.label 

1795 

1796 if unit or exp != 0: 

1797 if exp != 0: 

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

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

1800 else: 

1801 sunit = '[ %s ]' % unit 

1802 

1803 p = [] 

1804 if slabel: 

1805 p.append(slabel) 

1806 

1807 if sunit: 

1808 p.append(sunit) 

1809 

1810 return ' '.join(p) 

1811 

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

1813 override_scaled_unit_factor=None): 

1814 

1815 ''' 

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

1817 

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

1819 multiplier for given data range. 

1820 

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

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

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

1824 scaling applied. 

1825 ''' 

1826 

1827 sf = self.scaled_unit_factor 

1828 

1829 if override_scaled_unit_factor is not None: 

1830 sf = override_scaled_unit_factor 

1831 

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

1833 

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

1835 if self.inc is not None: 

1836 inc = self.inc*sf 

1837 

1838 if ax_projection: 

1839 exp = self.make_exp(inc) 

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

1841 unit = self.unit 

1842 else: 

1843 unit = self.scaled_unit 

1844 label = self.label_str(exp, unit) 

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

1846 else: 

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

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

1849 

1850 

1851class ScaleGuru(Guru): 

1852 

1853 ''' 

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

1855 

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

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

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

1859 arguments, which are required for most GMT commands. 

1860 

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

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

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

1864 

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

1866 

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

1868 limits imposed on other axes. 

1869 

1870 ''' 

1871 

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

1873 percent_interval=None, copy_from=None): 

1874 

1875 Guru.__init__(self) 

1876 

1877 if copy_from: 

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

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

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

1881 self.aspect = copy_from.aspect 

1882 

1883 if percent_interval is not None: 

1884 from scipy.stats import scoreatpercentile as scap 

1885 

1886 self.templates = dict( 

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

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

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

1890 

1891 maxdim = 2 

1892 if data_tuples: 

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

1894 else: 

1895 if axes: 

1896 maxdim = len(axes) 

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

1898 if axes is not None: 

1899 self.axes = axes 

1900 else: 

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

1902 

1903 # sophisticated data-range calculation 

1904 data_ranges = [None] * maxdim 

1905 for dt_ in data_tuples: 

1906 dt = num.asarray(dt_) 

1907 in_range = True 

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

1909 if ax.limits and ax.masking: 

1910 ax_limits = list(ax.limits) 

1911 if ax_limits[0] is None: 

1912 ax_limits[0] = -num.inf 

1913 if ax_limits[1] is None: 

1914 ax_limits[1] = num.inf 

1915 in_range = num.logical_and( 

1916 in_range, 

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

1918 

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

1920 

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

1922 if len(x) >= 1: 

1923 if in_range is not True: 

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

1925 if percent_interval is None: 

1926 range_this = ( 

1927 num.nanmin(xmasked), 

1928 num.nanmax(xmasked)) 

1929 else: 

1930 xmasked_finite = num.compress( 

1931 num.isfinite(xmasked), xmasked) 

1932 range_this = ( 

1933 scap(xmasked_finite, 

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

1935 scap(xmasked_finite, 

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

1937 else: 

1938 if percent_interval is None: 

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

1940 else: 

1941 xmasked_finite = num.compress( 

1942 num.isfinite(xmasked), xmasked) 

1943 range_this = ( 

1944 scap(xmasked_finite, 

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

1946 scap(xmasked_finite, 

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

1948 else: 

1949 range_this = (0., 1.) 

1950 

1951 if ax.limits: 

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

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

1954 range_this[1]) 

1955 

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

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

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

1959 

1960 else: 

1961 range_this = ax.limits 

1962 

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

1964 data_ranges[i] = range_this 

1965 else: 

1966 mi, ma = range_this 

1967 if data_ranges[i] is not None: 

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

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

1970 

1971 data_ranges[i] = (mi, ma) 

1972 

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

1974 if data_ranges[i] is None or not ( 

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

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

1977 

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

1979 

1980 self.data_ranges = data_ranges 

1981 self.aspect = aspect 

1982 

1983 def copy(self): 

1984 return ScaleGuru(copy_from=self) 

1985 

1986 def get_params(self, ax_projection=False): 

1987 

1988 ''' 

1989 Get dict with output parameters. 

1990 

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

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

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

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

1995 

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

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

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

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

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

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

2002 label string. 

2003 ''' 

2004 

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

2006 self.data_ranges[0], ax_projection) 

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

2008 self.data_ranges[1], ax_projection) 

2009 if len(self.axes) > 2: 

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

2011 self.data_ranges[2], ax_projection) 

2012 

2013 # enforce certain aspect, if needed 

2014 if self.aspect is not None: 

2015 xwid = xma-xmi 

2016 ywid = yma-ymi 

2017 if ywid < xwid*self.aspect: 

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

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

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

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

2022 override_scaled_unit_factor=1.) 

2023 

2024 elif xwid < ywid/self.aspect: 

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

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

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

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

2029 override_scaled_unit_factor=1.) 

2030 

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

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

2033 if len(self.axes) > 2: 

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

2035 

2036 return params 

2037 

2038 

2039class GumSpring(object): 

2040 

2041 ''' 

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

2043 ''' 

2044 

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

2046 self.minimal = minimal 

2047 if grow is None: 

2048 if minimal is None: 

2049 self.grow = 1.0 

2050 else: 

2051 self.grow = 0.0 

2052 else: 

2053 self.grow = grow 

2054 self.value = 1.0 

2055 

2056 def get_minimal(self): 

2057 if self.minimal is not None: 

2058 return self.minimal 

2059 else: 

2060 return 0.0 

2061 

2062 def get_grow(self): 

2063 return self.grow 

2064 

2065 def set_value(self, value): 

2066 self.value = value 

2067 

2068 def get_value(self): 

2069 return self.value 

2070 

2071 

2072def distribute(sizes, grows, space): 

2073 sizes = list(sizes) 

2074 gsum = sum(grows) 

2075 if gsum > 0.0: 

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

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

2078 return sizes 

2079 

2080 

2081class Widget(Guru): 

2082 

2083 ''' 

2084 Base class of the gmtpy layout system. 

2085 

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

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

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

2089 

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

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

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

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

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

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

2096 

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

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

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

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

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

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

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

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

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

2106 

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

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

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

2110 ''' 

2111 

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

2113 

2114 ''' 

2115 Create new widget. 

2116 ''' 

2117 

2118 Guru.__init__(self) 

2119 

2120 self.templates = dict( 

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

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

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

2124 

2125 if horizontal is None: 

2126 self.horizontal = GumSpring() 

2127 else: 

2128 self.horizontal = horizontal 

2129 

2130 if vertical is None: 

2131 self.vertical = GumSpring() 

2132 else: 

2133 self.vertical = vertical 

2134 

2135 self.aspect = None 

2136 self.parent = parent 

2137 self.dirty = True 

2138 

2139 def set_parent(self, parent): 

2140 

2141 ''' 

2142 Set the parent widget. 

2143 

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

2145 methods are responsible for calling this. 

2146 ''' 

2147 

2148 self.parent = parent 

2149 self.dirtyfy() 

2150 

2151 def get_parent(self): 

2152 

2153 ''' 

2154 Get the widgets parent widget. 

2155 ''' 

2156 

2157 return self.parent 

2158 

2159 def get_root(self): 

2160 

2161 ''' 

2162 Get the root widget in the layout hierarchy. 

2163 ''' 

2164 

2165 if self.parent is not None: 

2166 return self.get_parent() 

2167 else: 

2168 return self 

2169 

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

2171 

2172 ''' 

2173 Set the horizontal sizing policy of the Widget. 

2174 

2175 

2176 :param minimal: new minimal width of the widget 

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

2178 ''' 

2179 

2180 self.horizontal = GumSpring(minimal, grow) 

2181 self.dirtyfy() 

2182 

2183 def get_horizontal(self): 

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

2185 

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

2187 

2188 ''' 

2189 Set the horizontal sizing policy of the Widget. 

2190 

2191 :param minimal: new minimal height of the widget 

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

2193 ''' 

2194 

2195 self.vertical = GumSpring(minimal, grow) 

2196 self.dirtyfy() 

2197 

2198 def get_vertical(self): 

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

2200 

2201 def set_aspect(self, aspect=None): 

2202 

2203 ''' 

2204 Set aspect constraint on the widget. 

2205 

2206 The aspect is given as height divided by width. 

2207 ''' 

2208 

2209 self.aspect = aspect 

2210 self.dirtyfy() 

2211 

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

2213 

2214 ''' 

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

2216 call. 

2217 ''' 

2218 

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

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

2221 self.set_aspect(aspect) 

2222 

2223 def get_policy(self): 

2224 mh, gh = self.get_horizontal() 

2225 mv, gv = self.get_vertical() 

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

2227 

2228 def legalize(self, size, offset): 

2229 

2230 ''' 

2231 Get legal size for widget. 

2232 

2233 Returns: (new_size, new_offset) 

2234 

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

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

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

2238 ''' 

2239 

2240 sh, sv = size 

2241 oh, ov = offset 

2242 shs, svs = Widget.get_min_size(self) 

2243 ghs, gvs = Widget.get_grow(self) 

2244 

2245 if ghs == 0.0: 

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

2247 sh = shs 

2248 

2249 if gvs == 0.0: 

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

2251 sv = svs 

2252 

2253 if self.aspect is not None: 

2254 if sh > sv/self.aspect: 

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

2256 sh = sv/self.aspect 

2257 if sv > sh*self.aspect: 

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

2259 sv = sh*self.aspect 

2260 

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

2262 

2263 def get_min_size(self): 

2264 

2265 ''' 

2266 Get minimum size of widget. 

2267 

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

2269 ''' 

2270 

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

2272 if self.aspect is not None: 

2273 if mv == 0.0: 

2274 return mh, mh*self.aspect 

2275 elif mh == 0.0: 

2276 return mv/self.aspect, mv 

2277 return mh, mv 

2278 

2279 def get_grow(self): 

2280 

2281 ''' 

2282 Get widget's desire to grow. 

2283 

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

2285 ''' 

2286 

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

2288 

2289 def set_size(self, size, offset): 

2290 

2291 ''' 

2292 Set the widget's current size. 

2293 

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

2295 responsibility to call this. 

2296 ''' 

2297 

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

2299 self.offset = inner_offset 

2300 self.horizontal.set_value(sh) 

2301 self.vertical.set_value(sv) 

2302 self.dirty = False 

2303 

2304 def __str__(self): 

2305 

2306 def indent(ind, str): 

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

2308 size, offset = self.get_size() 

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

2310 children = self.get_children() 

2311 if children: 

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

2313 return s 

2314 

2315 def policies_debug_str(self): 

2316 

2317 def indent(ind, str): 

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

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

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

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

2322 

2323 children = self.get_children() 

2324 if children: 

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

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

2327 return s 

2328 

2329 def get_corners(self, descend=False): 

2330 

2331 ''' 

2332 Get coordinates of the corners of the widget. 

2333 

2334 Returns list with coordinate tuples. 

2335 

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

2337 coordinates of all sub-widgets. 

2338 ''' 

2339 

2340 self.do_layout() 

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

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

2343 if descend: 

2344 for child in self.get_children(): 

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

2346 return corners 

2347 

2348 def get_sizes(self): 

2349 

2350 ''' 

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

2352 

2353 Returns a list with size tuples. 

2354 ''' 

2355 self.do_layout() 

2356 sizes = [self.get_size()] 

2357 for child in self.get_children(): 

2358 sizes.extend(child.get_sizes()) 

2359 return sizes 

2360 

2361 def do_layout(self): 

2362 

2363 ''' 

2364 Triggers layouting of the widget hierarchy, if needed. 

2365 ''' 

2366 

2367 if self.parent is not None: 

2368 return self.parent.do_layout() 

2369 

2370 if not self.dirty: 

2371 return 

2372 

2373 sh, sv = self.get_min_size() 

2374 gh, gv = self.get_grow() 

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

2376 sh = 15.*cm 

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

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

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

2380 

2381 def get_children(self): 

2382 

2383 ''' 

2384 Get sub-widgets contained in this widget. 

2385 

2386 Returns a list of widgets. 

2387 ''' 

2388 

2389 return [] 

2390 

2391 def get_size(self): 

2392 

2393 ''' 

2394 Get current size and position of the widget. 

2395 

2396 Triggers layouting and returns 

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

2398 ''' 

2399 

2400 self.do_layout() 

2401 return (self.horizontal.get_value(), 

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

2403 

2404 def get_params(self): 

2405 

2406 ''' 

2407 Get current size and position of the widget. 

2408 

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

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

2411 ''' 

2412 

2413 self.do_layout() 

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

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

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

2417 

2418 def width(self): 

2419 

2420 ''' 

2421 Get current width of the widget. 

2422 

2423 Triggers layouting and returns width. 

2424 ''' 

2425 

2426 self.do_layout() 

2427 return self.horizontal.get_value() 

2428 

2429 def height(self): 

2430 

2431 ''' 

2432 Get current height of the widget. 

2433 

2434 Triggers layouting and return height. 

2435 ''' 

2436 

2437 self.do_layout() 

2438 return self.vertical.get_value() 

2439 

2440 def bbox(self): 

2441 

2442 ''' 

2443 Get PostScript bounding box for this widget. 

2444 

2445 Triggers layouting and returns values suitable to create PS bounding 

2446 box, representing the widgets current size and position. 

2447 ''' 

2448 

2449 self.do_layout() 

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

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

2452 

2453 def dirtyfy(self): 

2454 

2455 ''' 

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

2457 

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

2459 new layouting. 

2460 ''' 

2461 

2462 if self.parent is not None: 

2463 self.parent.dirtyfy() 

2464 

2465 self.dirty = True 

2466 

2467 

2468class CenterLayout(Widget): 

2469 

2470 ''' 

2471 A layout manager which centers its single child widget. 

2472 

2473 The child widget may be oversized. 

2474 ''' 

2475 

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

2477 Widget.__init__(self, horizontal, vertical) 

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

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

2480 

2481 def get_min_size(self): 

2482 shs, svs = Widget.get_min_size(self) 

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

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

2485 

2486 def get_grow(self): 

2487 ghs, gvs = Widget.get_grow(self) 

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

2489 return gh*ghs, gv*gvs 

2490 

2491 def set_size(self, size, offset): 

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

2493 

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

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

2496 if ghc != 0.: 

2497 shc = sh 

2498 if gvc != 0.: 

2499 svc = sv 

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

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

2502 

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

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

2505 

2506 def set_widget(self, widget=None): 

2507 

2508 ''' 

2509 Set the child widget, which shall be centered. 

2510 ''' 

2511 

2512 if widget is None: 

2513 widget = Widget() 

2514 

2515 self.content = widget 

2516 

2517 widget.set_parent(self) 

2518 

2519 def get_widget(self): 

2520 return self.content 

2521 

2522 def get_children(self): 

2523 return [self.content] 

2524 

2525 

2526class FrameLayout(Widget): 

2527 

2528 ''' 

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

2530 widgets. 

2531 

2532 :: 

2533 

2534 +---------------------------+ 

2535 | top | 

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

2537 | | | | 

2538 | left | center | right | 

2539 | | | | 

2540 +---------------------------+ 

2541 | bottom | 

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

2543 

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

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

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

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

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

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

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

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

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

2553 spaces between the widgets. 

2554 ''' 

2555 

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

2557 Widget.__init__(self, horizontal, vertical) 

2558 mw = 3.*cm 

2559 self.left = Widget( 

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

2561 self.right = Widget( 

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

2563 self.top = Widget( 

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

2565 parent=self) 

2566 self.bottom = Widget( 

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

2568 parent=self) 

2569 self.center = Widget( 

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

2571 parent=self) 

2572 

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

2574 ''' 

2575 Give margins fixed size constraints. 

2576 ''' 

2577 

2578 self.left.set_horizontal(left, 0) 

2579 self.right.set_horizontal(right, 0) 

2580 self.top.set_vertical(top, 0) 

2581 self.bottom.set_vertical(bottom, 0) 

2582 

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

2584 ''' 

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

2586 

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

2588 ''' 

2589 self.left.set_horizontal(left, grow) 

2590 self.right.set_horizontal(right, grow) 

2591 self.top.set_vertical(top, grow) 

2592 self.bottom.set_vertical(bottom, grow) 

2593 

2594 def get_min_size(self): 

2595 shs, svs = Widget.get_min_size(self) 

2596 

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

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

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

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

2601 

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

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

2604 

2605 # prevent widgets from collapsing 

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

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

2608 shsum += 0.1*cm 

2609 

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

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

2612 svsum += 0.1*cm 

2613 

2614 sh = max(shs, shsum) 

2615 sv = max(svs, svsum) 

2616 

2617 return sh, sv 

2618 

2619 def get_grow(self): 

2620 ghs, gvs = Widget.get_grow(self) 

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

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

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

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

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

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

2627 return gh, gv 

2628 

2629 def set_size(self, size, offset): 

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

2631 

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

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

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

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

2636 

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

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

2639 

2640 if ah < 0.0: 

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

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

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

2644 if av < 0.0: 

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

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

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

2648 

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

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

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

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

2653 

2654 if self.center.aspect is not None: 

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

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

2657 if 0.0 < ahm < ah: 

2658 slh, srh, sch = distribute( 

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

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

2661 

2662 elif 0.0 < avm < av: 

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

2664 sch*self.center.aspect), 

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

2666 

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

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

2669 

2670 oh += ah/2. 

2671 ov += av/2. 

2672 sh -= ah 

2673 sv -= av 

2674 

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

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

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

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

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

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

2681 

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

2683 

2684 ''' 

2685 Set one of the sub-widgets. 

2686 

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

2688 ``'bottom'`` or ``'center'``. 

2689 ''' 

2690 

2691 if widget is None: 

2692 widget = Widget() 

2693 

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

2695 self.__dict__[which] = widget 

2696 else: 

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

2698 

2699 widget.set_parent(self) 

2700 

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

2702 

2703 ''' 

2704 Get one of the sub-widgets. 

2705 

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

2707 ``'bottom'`` or ``'center'``. 

2708 ''' 

2709 

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

2711 return self.__dict__[which] 

2712 else: 

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

2714 

2715 def get_children(self): 

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

2717 

2718 

2719class GridLayout(Widget): 

2720 

2721 ''' 

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

2723 

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

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

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

2727 

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

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

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

2731 might not be resolved optimally. 

2732 ''' 

2733 

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

2735 

2736 ''' 

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

2738 ''' 

2739 

2740 Widget.__init__(self, horizontal, vertical) 

2741 self.grid = [] 

2742 for iy in range(ny): 

2743 row = [] 

2744 for ix in range(nx): 

2745 w = Widget(parent=self) 

2746 row.append(w) 

2747 

2748 self.grid.append(row) 

2749 

2750 def sub_min_sizes_as_array(self): 

2751 esh = num.array( 

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

2753 dtype=float) 

2754 esv = num.array( 

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

2756 dtype=float) 

2757 return esh, esv 

2758 

2759 def sub_grows_as_array(self): 

2760 egh = num.array( 

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

2762 dtype=float) 

2763 egv = num.array( 

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

2765 dtype=float) 

2766 return egh, egv 

2767 

2768 def get_min_size(self): 

2769 sh, sv = Widget.get_min_size(self) 

2770 esh, esv = self.sub_min_sizes_as_array() 

2771 if esh.size != 0: 

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

2773 if esv.size != 0: 

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

2775 return sh, sv 

2776 

2777 def get_grow(self): 

2778 ghs, gvs = Widget.get_grow(self) 

2779 egh, egv = self.sub_grows_as_array() 

2780 if egh.size != 0: 

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

2782 else: 

2783 gh = 1.0 

2784 if egv.size != 0: 

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

2786 else: 

2787 gv = 1.0 

2788 return gh, gv 

2789 

2790 def set_size(self, size, offset): 

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

2792 esh, esv = self.sub_min_sizes_as_array() 

2793 egh, egv = self.sub_grows_as_array() 

2794 

2795 # available additional space 

2796 empty = esh.size == 0 

2797 

2798 if not empty: 

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

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

2801 else: 

2802 av = sv 

2803 ah = sh 

2804 

2805 if ah < 0.0: 

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

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

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

2809 if av < 0.0: 

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

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

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

2813 

2814 nx, ny = esh.shape 

2815 

2816 if not empty: 

2817 # distribute additional space on rows and columns 

2818 # according to grow weights and minimal sizes 

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

2820 nesh = esh.copy() 

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

2822 

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

2824 

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

2826 nesv = esv.copy() 

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

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

2829 

2830 ah = sh - sum(nsh) 

2831 av = sv - sum(nsv) 

2832 

2833 oh += ah/2. 

2834 ov += av/2. 

2835 sh -= ah 

2836 sv -= av 

2837 

2838 # resize child widgets 

2839 neov = ov + sum(nsv) 

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

2841 neov -= nesv 

2842 neoh = oh 

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

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

2845 neoh += nesh 

2846 

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

2848 

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

2850 

2851 ''' 

2852 Set one of the sub-widgets. 

2853 

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

2855 counted from zero. 

2856 ''' 

2857 

2858 if widget is None: 

2859 widget = Widget() 

2860 

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

2862 widget.set_parent(self) 

2863 

2864 def get_widget(self, ix, iy): 

2865 

2866 ''' 

2867 Get one of the sub-widgets. 

2868 

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

2870 counted from zero. 

2871 ''' 

2872 

2873 return self.grid[iy][ix] 

2874 

2875 def get_children(self): 

2876 children = [] 

2877 for row in self.grid: 

2878 children.extend(row) 

2879 

2880 return children 

2881 

2882 

2883def is_gmt5(version='newest'): 

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

2885 

2886 

2887def is_gmt6(version='newest'): 

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

2889 

2890 

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

2892 

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

2894 

2895 if gmt.is_gmt5(): 

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

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

2898 gmt.save(fn, crop_eps_mode=True) 

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

2900 s = f.read() 

2901 

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

2903 else: 

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

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

2906 

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

2908 

2909 

2910def text_box( 

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

2912 

2913 gmt = GMT(version=gmtversion) 

2914 if gmt.is_gmt5(): 

2915 row = [0, 0, text] 

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

2917 else: 

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

2919 farg = [] 

2920 

2921 gmt.pstext( 

2922 in_rows=[row], 

2923 finish=True, 

2924 R=(0, 1, 0, 1), 

2925 J='x10p', 

2926 N=True, 

2927 *farg, 

2928 **kwargs) 

2929 

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

2931 gmt.save(fn) 

2932 

2933 (_, stderr) = subprocess.Popen( 

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

2935 stderr=subprocess.PIPE).communicate() 

2936 

2937 dx, dy = None, None 

2938 for line in stderr.splitlines(): 

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

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

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

2942 break 

2943 

2944 return dx, dy 

2945 

2946 

2947class TableLiner(object): 

2948 ''' 

2949 Utility class to turn tables into lines. 

2950 ''' 

2951 

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

2953 self.in_columns = in_columns 

2954 self.in_rows = in_rows 

2955 self.encoding = encoding 

2956 

2957 def __iter__(self): 

2958 if self.in_columns is not None: 

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

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

2961 self.encoding) 

2962 

2963 if self.in_rows is not None: 

2964 for row in self.in_rows: 

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

2966 self.encoding) 

2967 

2968 

2969class LineStreamChopper(object): 

2970 ''' 

2971 File-like object to buffer data. 

2972 ''' 

2973 

2974 def __init__(self, liner): 

2975 self.chopsize = None 

2976 self.liner = liner 

2977 self.chop_iterator = None 

2978 self.closed = False 

2979 

2980 def _chopiter(self): 

2981 buf = BytesIO() 

2982 for line in self.liner: 

2983 buf.write(line) 

2984 buflen = buf.tell() 

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

2986 buf.seek(0) 

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

2988 yield buf.read(self.chopsize) 

2989 

2990 newbuf = BytesIO() 

2991 newbuf.write(buf.read()) 

2992 buf.close() 

2993 buf = newbuf 

2994 

2995 yield buf.getvalue() 

2996 buf.close() 

2997 

2998 def read(self, size=None): 

2999 if self.closed: 

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

3001 if self.chop_iterator is None: 

3002 self.chopsize = size 

3003 self.chop_iterator = self._chopiter() 

3004 

3005 self.chopsize = size 

3006 try: 

3007 return next(self.chop_iterator) 

3008 except StopIteration: 

3009 return '' 

3010 

3011 def close(self): 

3012 self.chopsize = None 

3013 self.chop_iterator = None 

3014 self.closed = True 

3015 

3016 def flush(self): 

3017 pass 

3018 

3019 

3020font_tab = { 

3021 0: 'Helvetica', 

3022 1: 'Helvetica-Bold', 

3023} 

3024 

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

3026 

3027 

3028class GMT(object): 

3029 ''' 

3030 A thin wrapper to GMT command execution. 

3031 

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

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

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

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

3036 gmtpy and gmtpy must know where to find it. 

3037 

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

3039 output file. 

3040 

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

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

3043 

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

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

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

3047 

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

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

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

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

3052 

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

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

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

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

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

3058 execution of more than one GMT instance. 

3059 

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

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

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

3063 backward compatibility of the scripts can be maintained. 

3064 

3065 ''' 

3066 

3067 def __init__( 

3068 self, 

3069 config=None, 

3070 kontinue=None, 

3071 version='newest', 

3072 config_papersize=None, 

3073 eps_mode=False): 

3074 

3075 self.installation = get_gmt_installation(version) 

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

3077 self.eps_mode = eps_mode 

3078 self._shutil = shutil 

3079 

3080 if config: 

3081 self.gmt_config.update(config) 

3082 

3083 if config_papersize: 

3084 if not isinstance(config_papersize, str): 

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

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

3087 

3088 if self.is_gmt5(): 

3089 self.gmt_config['PS_MEDIA'] = config_papersize 

3090 else: 

3091 self.gmt_config['PAPER_MEDIA'] = config_papersize 

3092 

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

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

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

3096 

3097 if kontinue is not None: 

3098 self.load_unfinished(kontinue) 

3099 self.needstart = False 

3100 else: 

3101 self.output = BytesIO() 

3102 self.needstart = True 

3103 

3104 self.finished = False 

3105 

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

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

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

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

3110 

3111 self.layout = None 

3112 self.command_log = [] 

3113 self.keep_temp_dir = False 

3114 

3115 def is_gmt5(self): 

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

3117 

3118 def is_gmt6(self): 

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

3120 

3121 def get_version(self): 

3122 return self.installation['version'] 

3123 

3124 def get_config(self, key): 

3125 return self.gmt_config[key] 

3126 

3127 def to_points(self, string): 

3128 if not string: 

3129 return 0 

3130 

3131 unit = string[-1] 

3132 if unit in _units: 

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

3134 else: 

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

3136 return float(string)/_units[default_unit] 

3137 

3138 def label_font_size(self): 

3139 if self.is_gmt5(): 

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

3141 else: 

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

3143 

3144 def label_font(self): 

3145 if self.is_gmt5(): 

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

3147 else: 

3148 return self.gmt_config['LABEL_FONT'] 

3149 

3150 def gen_gmt_config_file(self, config_filename, config): 

3151 f = open(config_filename, 'wb') 

3152 f.write( 

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

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

3155 

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

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

3158 f.close() 

3159 

3160 def __del__(self): 

3161 if not self.keep_temp_dir: 

3162 self._shutil.rmtree(self.tempdir) 

3163 

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

3165 

3166 ''' 

3167 Execute arbitrary GMT command. 

3168 

3169 See docstring in __getattr__ for details. 

3170 ''' 

3171 

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

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

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

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

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

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

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

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

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

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

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

3183 

3184 assert not self.finished 

3185 

3186 # check for mutual exclusiveness on input and output possibilities 

3187 assert (1 >= len( 

3188 [x for x in [ 

3189 in_stream, in_filename, in_string, in_columns, in_rows] 

3190 if x is not None])) 

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

3192 if x is not None])) 

3193 

3194 options = [] 

3195 

3196 gmt_config = self.gmt_config 

3197 if not self.is_gmt5(): 

3198 gmt_config_filename = self.gmt_config_filename 

3199 if config_override: 

3200 gmt_config = self.gmt_config.copy() 

3201 gmt_config.update(config_override) 

3202 gmt_config_override_filename = pjoin( 

3203 self.tempdir, 'gmtdefaults_override') 

3204 self.gen_gmt_config_file( 

3205 gmt_config_override_filename, gmt_config) 

3206 gmt_config_filename = gmt_config_override_filename 

3207 

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

3209 if config_override: 

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

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

3212 

3213 if out_discard: 

3214 out_filename = '/dev/null' 

3215 

3216 out_mustclose = False 

3217 if out_filename is not None: 

3218 out_mustclose = True 

3219 out_stream = open(out_filename, 'wb') 

3220 

3221 if in_filename is not None: 

3222 in_stream = open(in_filename, 'rb') 

3223 

3224 if in_string is not None: 

3225 in_stream = BytesIO(in_string) 

3226 

3227 encoding_gmt = gmt_config.get( 

3228 'PS_CHAR_ENCODING', 

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

3230 

3231 encoding = encoding_gmt_to_python[encoding_gmt.lower()] 

3232 

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

3234 in_stream = LineStreamChopper(TableLiner(in_columns=in_columns, 

3235 in_rows=in_rows, 

3236 encoding=encoding)) 

3237 

3238 # convert option arguments to strings 

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

3240 if len(k) > 1: 

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

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

3243 % (k, command)) 

3244 

3245 if type(v) is bool: 

3246 if v: 

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

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

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

3250 else: 

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

3252 

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

3254 if out_stream is None: 

3255 if not finish: 

3256 options.append('-K') 

3257 else: 

3258 self.finished = True 

3259 

3260 if not self.needstart: 

3261 options.append('-O') 

3262 else: 

3263 self.needstart = False 

3264 

3265 out_stream = self.output 

3266 

3267 # run the command 

3268 if self.is_gmt5(): 

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

3270 else: 

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

3272 

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

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

3275 args.extend(options) 

3276 args.extend(addargs) 

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

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

3279 args.append('+'+gmt_config_filename) 

3280 

3281 bs = 2048 

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

3283 stdout=subprocess.PIPE, bufsize=bs, 

3284 env=self.environ) 

3285 while True: 

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

3287 if cr: 

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

3289 if cw: 

3290 if in_stream is not None: 

3291 data = in_stream.read(bs) 

3292 if len(data) == 0: 

3293 break 

3294 p.stdin.write(data) 

3295 else: 

3296 break 

3297 if not cr and not cw: 

3298 break 

3299 

3300 p.stdin.close() 

3301 

3302 while True: 

3303 data = p.stdout.read(bs) 

3304 if len(data) == 0: 

3305 break 

3306 out_stream.write(data) 

3307 

3308 p.stdout.close() 

3309 

3310 retcode = p.wait() 

3311 

3312 if in_stream is not None: 

3313 in_stream.close() 

3314 

3315 if out_mustclose: 

3316 out_stream.close() 

3317 

3318 if retcode != 0: 

3319 self.keep_temp_dir = True 

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

3321 'While executing command:\n%s' 

3322 % (command, escape_shell_args(args))) 

3323 

3324 self.command_log.append(args) 

3325 

3326 def __getattr__(self, command): 

3327 

3328 ''' 

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

3330 

3331 Execute arbitrary GMT command. 

3332 

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

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

3335 called. 

3336 

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

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

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

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

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

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

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

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

3345 

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

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

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

3349 not interested in the output. 

3350 

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

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

3353 

3354 =============== ======================================================= 

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

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

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

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

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

3360 ascii 

3361 table, which is fed to the process. 

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

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

3364 table, which is fed to the process. 

3365 =============== ======================================================= 

3366 

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

3368 following options: 

3369 

3370 ================= ===================================================== 

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

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

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

3374 ================= ===================================================== 

3375 

3376 Additional keyword arguments: 

3377 

3378 ===================== ================================================= 

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

3380 currently active set of defaults exclusively 

3381 during this call. 

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

3383 by the GMT instance is finished, and no further 

3384 plotting is allowed. 

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

3386 option to the command. 

3387 ===================== ================================================= 

3388 

3389 ''' 

3390 

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

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

3393 return f 

3394 

3395 def tempfilename(self, name=None): 

3396 ''' 

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

3398 

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

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

3401 ''' 

3402 

3403 if not name: 

3404 name = ''.join( 

3405 [random.choice('abcdefghijklmnopqrstuvwxyz') 

3406 for i in range(10)]) 

3407 

3408 fn = pjoin(self.tempdir, name) 

3409 return fn 

3410 

3411 def tempfile(self, name=None): 

3412 ''' 

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

3414 ''' 

3415 

3416 fn = self.tempfilename(name) 

3417 f = open(fn, 'wb') 

3418 return f, fn 

3419 

3420 def save_unfinished(self, filename): 

3421 out = open(filename, 'wb') 

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

3423 out.close() 

3424 

3425 def load_unfinished(self, filename): 

3426 self.output = BytesIO() 

3427 self.finished = False 

3428 inp = open(filename, 'rb') 

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

3430 inp.close() 

3431 

3432 def dump(self, ident): 

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

3434 self.save_unfinished(filename) 

3435 

3436 def load(self, ident): 

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

3438 self.load_unfinished(filename) 

3439 

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

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

3442 psconvert=False): 

3443 

3444 ''' 

3445 Finish and save figure as PDF, PS or PPM file. 

3446 

3447 If filename ends with ``'.pdf'`` a PDF file is created by piping the 

3448 GMT output through :program:`gmtpy-epstopdf`. 

3449 

3450 If filename ends with ``'.png'`` a PNG file is created by running 

3451 :program:`gmtpy-epstopdf`, :program:`pdftocairo` and 

3452 :program:`convert`. ``resolution`` specifies the resolution in DPI for 

3453 raster file formats. Rasterization is done at a higher resolution if 

3454 ``oversample`` is set to a value higher than one. The output image size 

3455 can also be controlled by setting ``width``, ``height`` or ``size`` 

3456 instead of ``resolution``. When ``size`` is given, the image is scaled 

3457 so that ``max(width, height) == size``. 

3458 

3459 The bounding box is set according to the values given in ``bbox``. 

3460 ''' 

3461 

3462 if not self.finished: 

3463 self.psxy(R=True, J=True, finish=True) 

3464 

3465 if filename: 

3466 tempfn = pjoin(self.tempdir, 'incomplete') 

3467 out = open(tempfn, 'wb') 

3468 else: 

3469 out = sys.stdout 

3470 

3471 if bbox and not self.is_gmt5(): 

3472 out.write(replace_bbox(bbox, self.output.getvalue())) 

3473 else: 

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

3475 

3476 if filename: 

3477 out.close() 

3478 

3479 if filename.endswith('.ps') or ( 

3480 not self.is_gmt5() and filename.endswith('.eps')): 

3481 

3482 shutil.move(tempfn, filename) 

3483 return 

3484 

3485 if self.is_gmt5(): 

3486 if crop_eps_mode: 

3487 addarg = ['-A'] 

3488 else: 

3489 addarg = [] 

3490 

3491 subprocess.call( 

3492 [pjoin(self.installation['bin'], 'gmt'), 'psconvert', 

3493 '-Te', '-F%s' % tempfn, tempfn, ] + addarg) 

3494 

3495 if bbox: 

3496 with open(tempfn + '.eps', 'rb') as fin: 

3497 with open(tempfn + '-fixbb.eps', 'wb') as fout: 

3498 replace_bbox(bbox, fin, fout) 

3499 

3500 shutil.move(tempfn + '-fixbb.eps', tempfn + '.eps') 

3501 

3502 else: 

3503 shutil.move(tempfn, tempfn + '.eps') 

3504 

3505 if filename.endswith('.eps'): 

3506 shutil.move(tempfn + '.eps', filename) 

3507 return 

3508 

3509 elif filename.endswith('.pdf'): 

3510 if psconvert: 

3511 gmt_bin = pjoin(self.installation['bin'], 'gmt') 

3512 subprocess.call([gmt_bin, 'psconvert', tempfn + '.eps', '-Tf', 

3513 '-F' + filename]) 

3514 else: 

3515 subprocess.call(['gmtpy-epstopdf', '--res=%i' % resolution, 

3516 '--outfile=' + filename, tempfn + '.eps']) 

3517 else: 

3518 subprocess.call([ 

3519 'gmtpy-epstopdf', 

3520 '--res=%i' % (resolution * oversample), 

3521 '--outfile=' + tempfn + '.pdf', tempfn + '.eps']) 

3522 

3523 convert_graph( 

3524 tempfn + '.pdf', filename, 

3525 resolution=resolution, oversample=oversample, 

3526 size=size, width=width, height=height) 

3527 

3528 def bbox(self): 

3529 return get_bbox(self.output.getvalue()) 

3530 

3531 def get_command_log(self): 

3532 ''' 

3533 Get the command log. 

3534 ''' 

3535 

3536 return self.command_log 

3537 

3538 def __str__(self): 

3539 s = '' 

3540 for com in self.command_log: 

3541 s += com[0] + '\n ' + '\n '.join(com[1:]) + '\n\n' 

3542 return s 

3543 

3544 def page_size_points(self): 

3545 ''' 

3546 Try to get paper size of output postscript file in points. 

3547 ''' 

3548 

3549 pm = paper_media(self.gmt_config).lower() 

3550 if pm.endswith('+') or pm.endswith('-'): 

3551 pm = pm[:-1] 

3552 

3553 orient = page_orientation(self.gmt_config).lower() 

3554 

3555 if pm in all_paper_sizes(): 

3556 

3557 if orient == 'portrait': 

3558 return get_paper_size(pm) 

3559 else: 

3560 return get_paper_size(pm)[1], get_paper_size(pm)[0] 

3561 

3562 m = re.match(r'custom_([0-9.]+)([cimp]?)x([0-9.]+)([cimp]?)', pm) 

3563 if m: 

3564 w, uw, h, uh = m.groups() 

3565 w, h = float(w), float(h) 

3566 if uw: 

3567 w *= _units[uw] 

3568 if uh: 

3569 h *= _units[uh] 

3570 if orient == 'portrait': 

3571 return w, h 

3572 else: 

3573 return h, w 

3574 

3575 return None, None 

3576 

3577 def default_layout(self, with_palette=False): 

3578 ''' 

3579 Get a default layout for the output page. 

3580 

3581 One of three different layouts is choosen, depending on the 

3582 `PAPER_MEDIA` setting in the GMT configuration dict. 

3583 

3584 If `PAPER_MEDIA` ends with a ``'+'`` (EPS output is selected), a 

3585 :py:class:`FrameLayout` is centered on the page, whose size is 

3586 controlled by its center widget's size plus the margins of the 

3587 :py:class:`FrameLayout`. 

3588 

3589 If `PAPER_MEDIA` indicates, that a custom page size is wanted by 

3590 starting with ``'Custom_'``, a :py:class:`FrameLayout` is used to fill 

3591 the complete page. The center widget's size is then controlled by the 

3592 page's size minus the margins of the :py:class:`FrameLayout`. 

3593 

3594 In any other case, two FrameLayouts are nested, such that the outer 

3595 layout attaches a 1 cm (printer) margin around the complete page, and 

3596 the inner FrameLayout's center widget takes up as much space as 

3597 possible under the constraint, that an aspect ratio of 1/golden_ratio 

3598 is preserved. 

3599 

3600 In any case, a reference to the innermost :py:class:`FrameLayout` 

3601 instance is returned. The top-level layout can be accessed by calling 

3602 :py:meth:`Widget.get_parent` on the returned layout. 

3603 ''' 

3604 

3605 if self.layout is None: 

3606 w, h = self.page_size_points() 

3607 

3608 if w is None or h is None: 

3609 raise GmtPyError("Can't determine page size for layout") 

3610 

3611 pm = paper_media(self.gmt_config).lower() 

3612 

3613 if with_palette: 

3614 palette_layout = GridLayout(3, 1) 

3615 spacer = palette_layout.get_widget(1, 0) 

3616 palette_widget = palette_layout.get_widget(2, 0) 

3617 spacer.set_horizontal(0.5*cm) 

3618 palette_widget.set_horizontal(0.5*cm) 

3619 

3620 if pm.endswith('+') or self.eps_mode: 

3621 outer = CenterLayout() 

3622 outer.set_policy((w, h), (0., 0.)) 

3623 inner = FrameLayout() 

3624 outer.set_widget(inner) 

3625 if with_palette: 

3626 inner.set_widget('center', palette_layout) 

3627 widget = palette_layout 

3628 else: 

3629 widget = inner.get_widget('center') 

3630 widget.set_policy((w/golden_ratio, 0.), (0., 0.), 

3631 aspect=1./golden_ratio) 

3632 mw = 3.0*cm 

3633 inner.set_fixed_margins( 

3634 mw, mw, mw/golden_ratio, mw/golden_ratio) 

3635 self.layout = inner 

3636 

3637 elif pm.startswith('custom_'): 

3638 layout = FrameLayout() 

3639 layout.set_policy((w, h), (0., 0.)) 

3640 mw = 3.0*cm 

3641 layout.set_min_margins( 

3642 mw, mw, mw/golden_ratio, mw/golden_ratio) 

3643 if with_palette: 

3644 layout.set_widget('center', palette_layout) 

3645 self.layout = layout 

3646 else: 

3647 outer = FrameLayout() 

3648 outer.set_policy((w, h), (0., 0.)) 

3649 outer.set_fixed_margins(1.*cm, 1.*cm, 1.*cm, 1.*cm) 

3650 

3651 inner = FrameLayout() 

3652 outer.set_widget('center', inner) 

3653 mw = 3.0*cm 

3654 inner.set_min_margins(mw, mw, mw/golden_ratio, mw/golden_ratio) 

3655 if with_palette: 

3656 inner.set_widget('center', palette_layout) 

3657 widget = palette_layout 

3658 else: 

3659 widget = inner.get_widget('center') 

3660 

3661 widget.set_aspect(1./golden_ratio) 

3662 

3663 self.layout = inner 

3664 

3665 return self.layout 

3666 

3667 def draw_layout(self, layout): 

3668 ''' 

3669 Use psxy to draw layout; for debugging 

3670 ''' 

3671 

3672 # corners = layout.get_corners(descend=True) 

3673 rects = num.array(layout.get_sizes(), dtype=float) 

3674 rects_wid = rects[:, 0, 0] 

3675 rects_hei = rects[:, 0, 1] 

3676 rects_center_x = rects[:, 1, 0] + rects_wid*0.5 

3677 rects_center_y = rects[:, 1, 1] + rects_hei*0.5 

3678 nrects = len(rects) 

3679 prects = (rects_center_x, rects_center_y, num.arange(nrects), 

3680 num.zeros(nrects), rects_hei, rects_wid) 

3681 

3682 # points = num.array(corners, dtype=float) 

3683 

3684 cptfile = self.tempfilename() + '.cpt' 

3685 self.makecpt( 

3686 C='ocean', 

3687 T='%g/%g/%g' % (-nrects, nrects, 1), 

3688 Z=True, 

3689 out_filename=cptfile, suppress_defaults=True) 

3690 

3691 bb = layout.bbox() 

3692 self.psxy( 

3693 in_columns=prects, 

3694 C=cptfile, 

3695 W='1p', 

3696 S='J', 

3697 R=(bb[0], bb[2], bb[1], bb[3]), 

3698 *layout.XYJ()) 

3699 

3700 

3701def simpleconf_to_ax(conf, axname): 

3702 c = {} 

3703 x = axname 

3704 for x in ('', axname): 

3705 for k in ('label', 'unit', 'scaled_unit', 'scaled_unit_factor', 

3706 'space', 'mode', 'approx_ticks', 'limits', 'masking', 'inc', 

3707 'snap'): 

3708 

3709 if x+k in conf: 

3710 c[k] = conf[x+k] 

3711 

3712 return Ax(**c) 

3713 

3714 

3715class DensityPlotDef(object): 

3716 def __init__(self, data, cpt='ocean', tension=0.7, size=(640, 480), 

3717 contour=False, method='surface', zscaler=None, **extra): 

3718 self.data = data 

3719 self.cpt = cpt 

3720 self.tension = tension 

3721 self.size = size 

3722 self.contour = contour 

3723 self.method = method 

3724 self.zscaler = zscaler 

3725 self.extra = extra 

3726 

3727 

3728class TextDef(object): 

3729 def __init__( 

3730 self, 

3731 data, 

3732 size=9, 

3733 justify='MC', 

3734 fontno=0, 

3735 offset=(0, 0), 

3736 color='black'): 

3737 

3738 self.data = data 

3739 self.size = size 

3740 self.justify = justify 

3741 self.fontno = fontno 

3742 self.offset = offset 

3743 self.color = color 

3744 

3745 

3746class Simple(object): 

3747 def __init__(self, gmtconfig=None, gmtversion='newest', **simple_config): 

3748 self.data = [] 

3749 self.symbols = [] 

3750 self.config = copy.deepcopy(simple_config) 

3751 self.gmtconfig = gmtconfig 

3752 self.density_plot_defs = [] 

3753 self.text_defs = [] 

3754 

3755 self.gmtversion = gmtversion 

3756 

3757 self.data_x = [] 

3758 self.symbols_x = [] 

3759 

3760 self.data_y = [] 

3761 self.symbols_y = [] 

3762 

3763 self.default_config = {} 

3764 self.set_defaults(width=15.*cm, 

3765 height=15.*cm / golden_ratio, 

3766 margins=(2.*cm, 2.*cm, 2.*cm, 2.*cm), 

3767 with_palette=False, 

3768 palette_offset=0.5*cm, 

3769 palette_width=None, 

3770 palette_height=None, 

3771 zlabeloffset=2*cm, 

3772 draw_layout=False) 

3773 

3774 self.setup_defaults() 

3775 self.fixate_widget_aspect = False 

3776 

3777 def setup_defaults(self): 

3778 pass 

3779 

3780 def set_defaults(self, **kwargs): 

3781 self.default_config.update(kwargs) 

3782 

3783 def plot(self, data, symbol=''): 

3784 self.data.append(data) 

3785 self.symbols.append(symbol) 

3786 

3787 def density_plot(self, data, **kwargs): 

3788 dpd = DensityPlotDef(data, **kwargs) 

3789 self.density_plot_defs.append(dpd) 

3790 

3791 def text(self, data, **kwargs): 

3792 dpd = TextDef(data, **kwargs) 

3793 self.text_defs.append(dpd) 

3794 

3795 def plot_x(self, data, symbol=''): 

3796 self.data_x.append(data) 

3797 self.symbols_x.append(symbol) 

3798 

3799 def plot_y(self, data, symbol=''): 

3800 self.data_y.append(data) 

3801 self.symbols_y.append(symbol) 

3802 

3803 def set(self, **kwargs): 

3804 self.config.update(kwargs) 

3805 

3806 def setup_base(self, conf): 

3807 w = conf.pop('width') 

3808 h = conf.pop('height') 

3809 margins = conf.pop('margins') 

3810 

3811 gmtconfig = {} 

3812 if self.gmtconfig is not None: 

3813 gmtconfig.update(self.gmtconfig) 

3814 

3815 gmt = GMT( 

3816 version=self.gmtversion, 

3817 config=gmtconfig, 

3818 config_papersize='Custom_%ix%i' % (w, h)) 

3819 

3820 layout = gmt.default_layout(with_palette=conf['with_palette']) 

3821 layout.set_min_margins(*margins) 

3822 if conf['with_palette']: 

3823 widget = layout.get_widget().get_widget(0, 0) 

3824 spacer = layout.get_widget().get_widget(1, 0) 

3825 spacer.set_horizontal(conf['palette_offset']) 

3826 palette_widget = layout.get_widget().get_widget(2, 0) 

3827 if conf['palette_width'] is not None: 

3828 palette_widget.set_horizontal(conf['palette_width']) 

3829 if conf['palette_height'] is not None: 

3830 palette_widget.set_vertical(conf['palette_height']) 

3831 widget.set_vertical(h-margins[2]-margins[3]-0.03*cm) 

3832 return gmt, layout, widget, palette_widget 

3833 else: 

3834 widget = layout.get_widget() 

3835 return gmt, layout, widget, None 

3836 

3837 def setup_projection(self, widget, scaler, conf): 

3838 pass 

3839 

3840 def setup_scaling(self, conf): 

3841 ndims = 2 

3842 if self.density_plot_defs: 

3843 ndims = 3 

3844 

3845 axes = [simpleconf_to_ax(conf, x) for x in 'xyz'[:ndims]] 

3846 

3847 data_all = [] 

3848 data_all.extend(self.data) 

3849 for dsd in self.density_plot_defs: 

3850 if dsd.zscaler is None: 

3851 data_all.append(dsd.data) 

3852 else: 

3853 data_all.append(dsd.data[:2]) 

3854 data_chopped = [ds[:ndims] for ds in data_all] 

3855 

3856 scaler = ScaleGuru(data_chopped, axes=axes[:ndims]) 

3857 

3858 self.setup_scaling_plus(scaler, axes[:ndims]) 

3859 

3860 return scaler 

3861 

3862 def setup_scaling_plus(self, scaler, axes): 

3863 pass 

3864 

3865 def setup_scaling_extra(self, scaler, conf): 

3866 

3867 scaler_x = scaler.copy() 

3868 scaler_x.data_ranges[1] = (0., 1.) 

3869 scaler_x.axes[1].mode = 'off' 

3870 

3871 scaler_y = scaler.copy() 

3872 scaler_y.data_ranges[0] = (0., 1.) 

3873 scaler_y.axes[0].mode = 'off' 

3874 

3875 return scaler_x, scaler_y 

3876 

3877 def draw_density(self, gmt, widget, scaler): 

3878 

3879 R = scaler.R() 

3880 # par = scaler.get_params() 

3881 rxyj = R + widget.XYJ() 

3882 innerticks = False 

3883 for dpd in self.density_plot_defs: 

3884 

3885 fn_cpt = gmt.tempfilename() + '.cpt' 

3886 

3887 if dpd.zscaler is not None: 

3888 s = dpd.zscaler 

3889 else: 

3890 s = scaler 

3891 

3892 gmt.makecpt(C=dpd.cpt, out_filename=fn_cpt, *s.T()) 

3893 

3894 fn_grid = gmt.tempfilename() 

3895 

3896 fn_mean = gmt.tempfilename() 

3897 

3898 if dpd.method in ('surface', 'triangulate'): 

3899 gmt.blockmean(in_columns=dpd.data, 

3900 I='%i+/%i+' % dpd.size, # noqa 

3901 out_filename=fn_mean, *R) 

3902 

3903 if dpd.method == 'surface': 

3904 gmt.surface( 

3905 in_filename=fn_mean, 

3906 T=dpd.tension, 

3907 G=fn_grid, 

3908 I='%i+/%i+' % dpd.size, # noqa 

3909 out_discard=True, 

3910 *R) 

3911 

3912 if dpd.method == 'triangulate': 

3913 gmt.triangulate( 

3914 in_filename=fn_mean, 

3915 G=fn_grid, 

3916 I='%i+/%i+' % dpd.size, # noqa 

3917 out_discard=True, 

3918 V=True, 

3919 *R) 

3920 

3921 if gmt.is_gmt5(): 

3922 gmt.grdimage(fn_grid, C=fn_cpt, E='i', n='l', *rxyj) 

3923 

3924 else: 

3925 gmt.grdimage(fn_grid, C=fn_cpt, E='i', S='l', *rxyj) 

3926 

3927 if dpd.contour: 

3928 gmt.grdcontour(fn_grid, C=fn_cpt, W='0.5p,black', *rxyj) 

3929 innerticks = '0.5p,black' 

3930 

3931 os.remove(fn_grid) 

3932 os.remove(fn_mean) 

3933 

3934 if dpd.method == 'fillcontour': 

3935 extra = dict(C=fn_cpt) 

3936 extra.update(dpd.extra) 

3937 gmt.pscontour(in_columns=dpd.data, 

3938 I=True, *rxyj, **extra) # noqa 

3939 

3940 if dpd.method == 'contour': 

3941 extra = dict(W='0.5p,black', C=fn_cpt) 

3942 extra.update(dpd.extra) 

3943 gmt.pscontour(in_columns=dpd.data, *rxyj, **extra) 

3944 

3945 return fn_cpt, innerticks 

3946 

3947 def draw_basemap(self, gmt, widget, scaler): 

3948 gmt.psbasemap(*(widget.JXY() + scaler.RB(ax_projection=True))) 

3949 

3950 def draw(self, gmt, widget, scaler): 

3951 rxyj = scaler.R() + widget.JXY() 

3952 for dat, sym in zip(self.data, self.symbols): 

3953 gmt.psxy(in_columns=dat, *(sym.split()+rxyj)) 

3954 

3955 def post_draw(self, gmt, widget, scaler): 

3956 pass 

3957 

3958 def pre_draw(self, gmt, widget, scaler): 

3959 pass 

3960 

3961 def draw_extra(self, gmt, widget, scaler_x, scaler_y): 

3962 

3963 for dat, sym in zip(self.data_x, self.symbols_x): 

3964 gmt.psxy(in_columns=dat, 

3965 *(sym.split() + scaler_x.R() + widget.JXY())) 

3966 

3967 for dat, sym in zip(self.data_y, self.symbols_y): 

3968 gmt.psxy(in_columns=dat, 

3969 *(sym.split() + scaler_y.R() + widget.JXY())) 

3970 

3971 def draw_text(self, gmt, widget, scaler): 

3972 

3973 rxyj = scaler.R() + widget.JXY() 

3974 for td in self.text_defs: 

3975 x, y = td.data[0:2] 

3976 text = td.data[-1] 

3977 size = td.size 

3978 angle = 0 

3979 fontno = td.fontno 

3980 justify = td.justify 

3981 color = td.color 

3982 if gmt.is_gmt5(): 

3983 gmt.pstext( 

3984 in_rows=[(x, y, text)], 

3985 F='+f%gp,%s,%s+a%g+j%s' % ( 

3986 size, fontno, color, angle, justify), 

3987 D='%gp/%gp' % td.offset, *rxyj) 

3988 else: 

3989 gmt.pstext( 

3990 in_rows=[(x, y, size, angle, fontno, justify, text)], 

3991 D='%gp/%gp' % td.offset, *rxyj) 

3992 

3993 def save(self, filename, resolution=150): 

3994 

3995 conf = dict(self.default_config) 

3996 conf.update(self.config) 

3997 

3998 gmt, layout, widget, palette_widget = self.setup_base(conf) 

3999 scaler = self.setup_scaling(conf) 

4000 scaler_x, scaler_y = self.setup_scaling_extra(scaler, conf) 

4001 

4002 self.setup_projection(widget, scaler, conf) 

4003 if self.fixate_widget_aspect: 

4004 aspect = aspect_for_projection( 

4005 gmt.installation['version'], *(widget.J() + scaler.R())) 

4006 

4007 widget.set_aspect(aspect) 

4008 

4009 if conf['draw_layout']: 

4010 gmt.draw_layout(layout) 

4011 cptfile = None 

4012 if self.density_plot_defs: 

4013 cptfile, innerticks = self.draw_density(gmt, widget, scaler) 

4014 self.pre_draw(gmt, widget, scaler) 

4015 self.draw(gmt, widget, scaler) 

4016 self.post_draw(gmt, widget, scaler) 

4017 self.draw_extra(gmt, widget, scaler_x, scaler_y) 

4018 self.draw_text(gmt, widget, scaler) 

4019 self.draw_basemap(gmt, widget, scaler) 

4020 

4021 if palette_widget and cptfile: 

4022 nice_palette(gmt, palette_widget, scaler, cptfile, 

4023 innerticks=innerticks, 

4024 zlabeloffset=conf['zlabeloffset']) 

4025 

4026 gmt.save(filename, resolution=resolution) 

4027 

4028 

4029class LinLinPlot(Simple): 

4030 pass 

4031 

4032 

4033class LogLinPlot(Simple): 

4034 

4035 def setup_defaults(self): 

4036 self.set_defaults(xmode='min-max') 

4037 

4038 def setup_projection(self, widget, scaler, conf): 

4039 widget['J'] = '-JX%(width)gpl/%(height)gp' 

4040 scaler['B'] = '-B2:%(xlabel)s:/%(yinc)g:%(ylabel)s:WSen' 

4041 

4042 

4043class LinLogPlot(Simple): 

4044 

4045 def setup_defaults(self): 

4046 self.set_defaults(ymode='min-max') 

4047 

4048 def setup_projection(self, widget, scaler, conf): 

4049 widget['J'] = '-JX%(width)gp/%(height)gpl' 

4050 scaler['B'] = '-B%(xinc)g:%(xlabel)s:/2:%(ylabel)s:WSen' 

4051 

4052 

4053class LogLogPlot(Simple): 

4054 

4055 def setup_defaults(self): 

4056 self.set_defaults(mode='min-max') 

4057 

4058 def setup_projection(self, widget, scaler, conf): 

4059 widget['J'] = '-JX%(width)gpl/%(height)gpl' 

4060 scaler['B'] = '-B2:%(xlabel)s:/2:%(ylabel)s:WSen' 

4061 

4062 

4063class AziDistPlot(Simple): 

4064 

4065 def __init__(self, *args, **kwargs): 

4066 Simple.__init__(self, *args, **kwargs) 

4067 self.fixate_widget_aspect = True 

4068 

4069 def setup_defaults(self): 

4070 self.set_defaults( 

4071 height=15.*cm, 

4072 width=15.*cm, 

4073 xmode='off', 

4074 xlimits=(0., 360.), 

4075 xinc=45.) 

4076 

4077 def setup_projection(self, widget, scaler, conf): 

4078 widget['J'] = '-JPa%(width)gp' 

4079 

4080 def setup_scaling_plus(self, scaler, axes): 

4081 scaler['B'] = '-B%(xinc)g:%(xlabel)s:/%(yinc)g:%(ylabel)s:N' 

4082 

4083 

4084class MPlot(Simple): 

4085 

4086 def __init__(self, *args, **kwargs): 

4087 Simple.__init__(self, *args, **kwargs) 

4088 self.fixate_widget_aspect = True 

4089 

4090 def setup_defaults(self): 

4091 self.set_defaults(xmode='min-max', ymode='min-max') 

4092 

4093 def setup_projection(self, widget, scaler, conf): 

4094 par = scaler.get_params() 

4095 lon0 = (par['xmin'] + par['xmax'])/2. 

4096 lat0 = (par['ymin'] + par['ymax'])/2. 

4097 sll = '%g/%g' % (lon0, lat0) 

4098 widget['J'] = '-JM' + sll + '/%(width)gp' 

4099 scaler['B'] = \ 

4100 '-B%(xinc)gg%(xinc)g:%(xlabel)s:/%(yinc)gg%(yinc)g:%(ylabel)s:WSen' 

4101 

4102 

4103def nice_palette(gmt, widget, scaleguru, cptfile, zlabeloffset=0.8*inch, 

4104 innerticks=True): 

4105 

4106 par = scaleguru.get_params() 

4107 par_ax = scaleguru.get_params(ax_projection=True) 

4108 nz_palette = int(widget.height()/inch * 300) 

4109 px = num.zeros(nz_palette*2) 

4110 px[1::2] += 1 

4111 pz = num.linspace(par['zmin'], par['zmax'], nz_palette).repeat(2) 

4112 pdz = pz[2]-pz[0] 

4113 palgrdfile = gmt.tempfilename() 

4114 pal_r = (0, 1, par['zmin'], par['zmax']) 

4115 pal_ax_r = (0, 1, par_ax['zmin'], par_ax['zmax']) 

4116 gmt.xyz2grd( 

4117 G=palgrdfile, R=pal_r, 

4118 I=(1, pdz), in_columns=(px, pz, pz), # noqa 

4119 out_discard=True) 

4120 

4121 gmt.grdimage(palgrdfile, R=pal_r, C=cptfile, *widget.JXY()) 

4122 if isinstance(innerticks, str): 

4123 tickpen = innerticks 

4124 gmt.grdcontour(palgrdfile, W=tickpen, R=pal_r, C=cptfile, 

4125 *widget.JXY()) 

4126 

4127 negpalwid = '%gp' % -widget.width() 

4128 if not isinstance(innerticks, str) and innerticks: 

4129 ticklen = negpalwid 

4130 else: 

4131 ticklen = '0p' 

4132 

4133 TICK_LENGTH_PARAM = 'MAP_TICK_LENGTH' if gmt.is_gmt5() else 'TICK_LENGTH' 

4134 gmt.psbasemap( 

4135 R=pal_ax_r, B='4::/%(zinc)g::nsw' % par_ax, 

4136 config={TICK_LENGTH_PARAM: ticklen}, 

4137 *widget.JXY()) 

4138 

4139 if innerticks: 

4140 gmt.psbasemap( 

4141 R=pal_ax_r, B='4::/%(zinc)g::E' % par_ax, 

4142 config={TICK_LENGTH_PARAM: '0p'}, 

4143 *widget.JXY()) 

4144 else: 

4145 gmt.psbasemap(R=pal_ax_r, B='4::/%(zinc)g::E' % par_ax, *widget.JXY()) 

4146 

4147 if par_ax['zlabel']: 

4148 label_font = gmt.label_font() 

4149 label_font_size = gmt.label_font_size() 

4150 label_offset = zlabeloffset 

4151 gmt.pstext( 

4152 R=(0, 1, 0, 2), D='%gp/0p' % label_offset, 

4153 N=True, 

4154 in_rows=[(1, 1, label_font_size, -90, label_font, 'CB', 

4155 par_ax['zlabel'])], 

4156 *widget.JXY())