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

1635 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-10-12 12:00 +0000

1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

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

5''' 

6A Python interface to GMT. 

7''' 

8 

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

10# See there for copying and licensing information. 

11 

12import subprocess 

13try: 

14 from StringIO import StringIO as BytesIO 

15except ImportError: 

16 from io import BytesIO 

17import re 

18import os 

19import sys 

20import shutil 

21from os.path import join as pjoin 

22import tempfile 

23import random 

24import logging 

25import math 

26import numpy as num 

27import copy 

28from select import select 

29try: 

30 from scipy.io import netcdf_file 

31except ImportError: 

32 from scipy.io.netcdf import netcdf_file 

33 

34from pyrocko import ExternalProgramMissing 

35from . import AutoScaler 

36 

37 

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

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

40 

41 

42encoding_gmt_to_python = { 

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

44 'standard+': 'ascii', 

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

46 'standard': 'ascii'} 

47 

48for i in range(1, 11): 

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

50 

51 

52def have_gmt(): 

53 try: 

54 get_gmt_installation('newest') 

55 return True 

56 

57 except GMTInstallationProblem: 

58 return False 

59 

60 

61def check_have_gmt(): 

62 if not have_gmt(): 

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

64 

65 

66def have_pixmaptools(): 

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

68 try: 

69 p = subprocess.Popen( 

70 prog, 

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

72 

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

74 

75 except OSError: 

76 return False 

77 

78 return True 

79 

80 

81class GmtPyError(Exception): 

82 pass 

83 

84 

85class GMTError(GmtPyError): 

86 pass 

87 

88 

89class GMTInstallationProblem(GmtPyError): 

90 pass 

91 

92 

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

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

95 

96 _, tmp_filename_base = tempfile.mkstemp() 

97 

98 try: 

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

100 fmt_arg = '-svg' 

101 tmp_filename = tmp_filename_base 

102 oversample = 1.0 

103 else: 

104 fmt_arg = '-png' 

105 tmp_filename = tmp_filename_base + '-1.png' 

106 

107 if size is not None: 

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

109 elif width is not None: 

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

111 elif height is not None: 

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

113 else: 

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

115 

116 try: 

117 subprocess.check_call( 

118 ['pdftocairo'] + scale_args + 

119 [fmt_arg, in_filename, tmp_filename_base]) 

120 except OSError as e: 

121 raise GmtPyError( 

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

123 

124 if oversample > 1.: 

125 try: 

126 subprocess.check_call([ 

127 'convert', 

128 tmp_filename, 

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

130 out_filename]) 

131 except OSError as e: 

132 raise GmtPyError( 

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

134 

135 else: 

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

137 shutil.move(tmp_filename, out_filename) 

138 else: 

139 try: 

140 subprocess.check_call( 

141 ['convert', tmp_filename, out_filename]) 

142 except Exception as e: 

143 raise GmtPyError( 

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

145 % str(e)) 

146 

147 except Exception: 

148 raise 

149 

150 finally: 

151 if os.path.exists(tmp_filename_base): 

152 os.remove(tmp_filename_base) 

153 

154 if os.path.exists(tmp_filename): 

155 os.remove(tmp_filename) 

156 

157 

158def get_bbox(s): 

159 for pat in [find_hiresbb, find_bb]: 

160 m = pat.search(s) 

161 if m: 

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

163 return bb 

164 

165 raise GmtPyError('Cannot find bbox') 

166 

167 

168def replace_bbox(bbox, *args): 

169 

170 def repl(m): 

171 if m.group(1): 

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

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

174 else: 

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

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

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

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

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

180 

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

182 if len(args) == 1: 

183 s = args[0] 

184 return pat.sub(repl, s) 

185 

186 else: 

187 fin, fout = args 

188 nn = 0 

189 for line in fin: 

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

191 nn += n 

192 fout.write(line) 

193 if nn == 2: 

194 break 

195 

196 if nn == 2: 

197 for line in fin: 

198 fout.write(line) 

199 

200 

201def escape_shell_arg(s): 

202 ''' 

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

204 insecure. 

205 ''' 

206 

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

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

209 else: 

210 return s 

211 

212 

213def escape_shell_args(args): 

214 ''' 

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

216 insecure. 

217 ''' 

218 

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

220 

221 

222golden_ratio = 1.61803 

223 

224# units in points 

225_units = { 

226 'i': 72., 

227 'c': 72./2.54, 

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

229 'p': 1.} 

230 

231inch = _units['i'] 

232''' 

233Unit cm in points. 

234''' 

235 

236 

237cm = _units['c'] 

238''' 

239Unit inch in points. 

240''' 

241 

242# some awsome colors 

243tango_colors = { 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

271} 

272 

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

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

275 'butter2')] 

276 

277 

278def color(x=None): 

279 ''' 

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

281 

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

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

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

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

286 transformed into the string form which GMT expects. 

287 ''' 

288 

289 if x is None: 

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

291 

292 if isinstance(x, int): 

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

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

295 else: 

296 return '0/0/0' 

297 

298 elif isinstance(x, str): 

299 if x in tango_colors: 

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

301 else: 

302 return x 

303 

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

305 

306 

307def color_tup(x=None): 

308 if x is None: 

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

310 

311 if isinstance(x, int): 

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

313 return graph_colors[x] 

314 else: 

315 return (0, 0, 0) 

316 

317 elif isinstance(x, str): 

318 if x in tango_colors: 

319 return tango_colors[x] 

320 

321 return x 

322 

323 

324_gmt_installations = {} 

325 

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

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

328 

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

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

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

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

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

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

335 

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

337 

338 

339def key_version(a): 

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

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

342 

343 

344def newest_installed_gmt_version(): 

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

346 

347 

348def all_installed_gmt_versions(): 

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

350 

351 

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

353# changed. 

354 

355_gmt_defaults_by_version = {} 

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

357# 

358# GMT-SYSTEM 4.2.1 Defaults file 

359# 

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

361PAGE_COLOR = 255/255/255 

362PAGE_ORIENTATION = portrait 

363PAPER_MEDIA = a4+ 

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

365ANNOT_MIN_ANGLE = 20 

366ANNOT_MIN_SPACING = 0 

367ANNOT_FONT_PRIMARY = Helvetica 

368ANNOT_FONT_SIZE = 12p 

369ANNOT_OFFSET_PRIMARY = 0.075i 

370ANNOT_FONT_SECONDARY = Helvetica 

371ANNOT_FONT_SIZE_SECONDARY = 16p 

372ANNOT_OFFSET_SECONDARY = 0.075i 

373DEGREE_SYMBOL = ring 

374HEADER_FONT = Helvetica 

375HEADER_FONT_SIZE = 36p 

376HEADER_OFFSET = 0.1875i 

377LABEL_FONT = Helvetica 

378LABEL_FONT_SIZE = 14p 

379LABEL_OFFSET = 0.1125i 

380OBLIQUE_ANNOTATION = 1 

381PLOT_CLOCK_FORMAT = hh:mm:ss 

382PLOT_DATE_FORMAT = yyyy-mm-dd 

383PLOT_DEGREE_FORMAT = +ddd:mm:ss 

384Y_AXIS_TYPE = hor_text 

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

386BASEMAP_AXES = WESN 

387BASEMAP_FRAME_RGB = 0/0/0 

388BASEMAP_TYPE = plain 

389FRAME_PEN = 1.25p 

390FRAME_WIDTH = 0.075i 

391GRID_CROSS_SIZE_PRIMARY = 0i 

392GRID_CROSS_SIZE_SECONDARY = 0i 

393GRID_PEN_PRIMARY = 0.25p 

394GRID_PEN_SECONDARY = 0.5p 

395MAP_SCALE_HEIGHT = 0.075i 

396TICK_LENGTH = 0.075i 

397POLAR_CAP = 85/90 

398TICK_PEN = 0.5p 

399X_AXIS_LENGTH = 9i 

400Y_AXIS_LENGTH = 6i 

401X_ORIGIN = 1i 

402Y_ORIGIN = 1i 

403UNIX_TIME = FALSE 

404UNIX_TIME_POS = -0.75i/-0.75i 

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

406COLOR_BACKGROUND = 0/0/0 

407COLOR_FOREGROUND = 255/255/255 

408COLOR_NAN = 128/128/128 

409COLOR_IMAGE = adobe 

410COLOR_MODEL = rgb 

411HSV_MIN_SATURATION = 1 

412HSV_MAX_SATURATION = 0.1 

413HSV_MIN_VALUE = 0.3 

414HSV_MAX_VALUE = 1 

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

416CHAR_ENCODING = ISOLatin1+ 

417DOTS_PR_INCH = 300 

418N_COPIES = 1 

419PS_COLOR = rgb 

420PS_IMAGE_COMPRESS = none 

421PS_IMAGE_FORMAT = ascii 

422PS_LINE_CAP = round 

423PS_LINE_JOIN = miter 

424PS_MITER_LIMIT = 35 

425PS_VERBOSE = FALSE 

426GLOBAL_X_SCALE = 1 

427GLOBAL_Y_SCALE = 1 

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

429D_FORMAT = %lg 

430FIELD_DELIMITER = tab 

431GRIDFILE_SHORTHAND = FALSE 

432GRID_FORMAT = nf 

433INPUT_CLOCK_FORMAT = hh:mm:ss 

434INPUT_DATE_FORMAT = yyyy-mm-dd 

435IO_HEADER = FALSE 

436N_HEADER_RECS = 1 

437OUTPUT_CLOCK_FORMAT = hh:mm:ss 

438OUTPUT_DATE_FORMAT = yyyy-mm-dd 

439OUTPUT_DEGREE_FORMAT = +D 

440XY_TOGGLE = FALSE 

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

442ELLIPSOID = WGS-84 

443MAP_SCALE_FACTOR = default 

444MEASURE_UNIT = inch 

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

446TIME_FORMAT_PRIMARY = full 

447TIME_FORMAT_SECONDARY = full 

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

449TIME_IS_INTERVAL = OFF 

450TIME_INTERVAL_FRACTION = 0.5 

451TIME_LANGUAGE = us 

452TIME_SYSTEM = other 

453TIME_UNIT = d 

454TIME_WEEK_START = Sunday 

455Y2K_OFFSET_YEAR = 1950 

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

457HISTORY = TRUE 

458INTERPOLANT = akima 

459LINE_STEP = 0.01i 

460VECTOR_SHAPE = 0 

461VERBOSE = FALSE''' 

462 

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

464# 

465# GMT-SYSTEM 4.3.0 Defaults file 

466# 

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

468PAGE_COLOR = 255/255/255 

469PAGE_ORIENTATION = portrait 

470PAPER_MEDIA = a4+ 

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

472ANNOT_MIN_ANGLE = 20 

473ANNOT_MIN_SPACING = 0 

474ANNOT_FONT_PRIMARY = Helvetica 

475ANNOT_FONT_SIZE_PRIMARY = 12p 

476ANNOT_OFFSET_PRIMARY = 0.075i 

477ANNOT_FONT_SECONDARY = Helvetica 

478ANNOT_FONT_SIZE_SECONDARY = 16p 

479ANNOT_OFFSET_SECONDARY = 0.075i 

480DEGREE_SYMBOL = ring 

481HEADER_FONT = Helvetica 

482HEADER_FONT_SIZE = 36p 

483HEADER_OFFSET = 0.1875i 

484LABEL_FONT = Helvetica 

485LABEL_FONT_SIZE = 14p 

486LABEL_OFFSET = 0.1125i 

487OBLIQUE_ANNOTATION = 1 

488PLOT_CLOCK_FORMAT = hh:mm:ss 

489PLOT_DATE_FORMAT = yyyy-mm-dd 

490PLOT_DEGREE_FORMAT = +ddd:mm:ss 

491Y_AXIS_TYPE = hor_text 

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

493BASEMAP_AXES = WESN 

494BASEMAP_FRAME_RGB = 0/0/0 

495BASEMAP_TYPE = plain 

496FRAME_PEN = 1.25p 

497FRAME_WIDTH = 0.075i 

498GRID_CROSS_SIZE_PRIMARY = 0i 

499GRID_PEN_PRIMARY = 0.25p 

500GRID_CROSS_SIZE_SECONDARY = 0i 

501GRID_PEN_SECONDARY = 0.5p 

502MAP_SCALE_HEIGHT = 0.075i 

503POLAR_CAP = 85/90 

504TICK_LENGTH = 0.075i 

505TICK_PEN = 0.5p 

506X_AXIS_LENGTH = 9i 

507Y_AXIS_LENGTH = 6i 

508X_ORIGIN = 1i 

509Y_ORIGIN = 1i 

510UNIX_TIME = FALSE 

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

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

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

514COLOR_BACKGROUND = 0/0/0 

515COLOR_FOREGROUND = 255/255/255 

516COLOR_NAN = 128/128/128 

517COLOR_IMAGE = adobe 

518COLOR_MODEL = rgb 

519HSV_MIN_SATURATION = 1 

520HSV_MAX_SATURATION = 0.1 

521HSV_MIN_VALUE = 0.3 

522HSV_MAX_VALUE = 1 

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

524CHAR_ENCODING = ISOLatin1+ 

525DOTS_PR_INCH = 300 

526N_COPIES = 1 

527PS_COLOR = rgb 

528PS_IMAGE_COMPRESS = none 

529PS_IMAGE_FORMAT = ascii 

530PS_LINE_CAP = round 

531PS_LINE_JOIN = miter 

532PS_MITER_LIMIT = 35 

533PS_VERBOSE = FALSE 

534GLOBAL_X_SCALE = 1 

535GLOBAL_Y_SCALE = 1 

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

537D_FORMAT = %lg 

538FIELD_DELIMITER = tab 

539GRIDFILE_SHORTHAND = FALSE 

540GRID_FORMAT = nf 

541INPUT_CLOCK_FORMAT = hh:mm:ss 

542INPUT_DATE_FORMAT = yyyy-mm-dd 

543IO_HEADER = FALSE 

544N_HEADER_RECS = 1 

545OUTPUT_CLOCK_FORMAT = hh:mm:ss 

546OUTPUT_DATE_FORMAT = yyyy-mm-dd 

547OUTPUT_DEGREE_FORMAT = +D 

548XY_TOGGLE = FALSE 

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

550ELLIPSOID = WGS-84 

551MAP_SCALE_FACTOR = default 

552MEASURE_UNIT = inch 

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

554TIME_FORMAT_PRIMARY = full 

555TIME_FORMAT_SECONDARY = full 

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

557TIME_IS_INTERVAL = OFF 

558TIME_INTERVAL_FRACTION = 0.5 

559TIME_LANGUAGE = us 

560TIME_UNIT = d 

561TIME_WEEK_START = Sunday 

562Y2K_OFFSET_YEAR = 1950 

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

564HISTORY = TRUE 

565INTERPOLANT = akima 

566LINE_STEP = 0.01i 

567VECTOR_SHAPE = 0 

568VERBOSE = FALSE''' 

569 

570 

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

572# 

573# GMT-SYSTEM 4.3.1 Defaults file 

574# 

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

576PAGE_COLOR = 255/255/255 

577PAGE_ORIENTATION = portrait 

578PAPER_MEDIA = a4+ 

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

580ANNOT_MIN_ANGLE = 20 

581ANNOT_MIN_SPACING = 0 

582ANNOT_FONT_PRIMARY = Helvetica 

583ANNOT_FONT_SIZE_PRIMARY = 12p 

584ANNOT_OFFSET_PRIMARY = 0.075i 

585ANNOT_FONT_SECONDARY = Helvetica 

586ANNOT_FONT_SIZE_SECONDARY = 16p 

587ANNOT_OFFSET_SECONDARY = 0.075i 

588DEGREE_SYMBOL = ring 

589HEADER_FONT = Helvetica 

590HEADER_FONT_SIZE = 36p 

591HEADER_OFFSET = 0.1875i 

592LABEL_FONT = Helvetica 

593LABEL_FONT_SIZE = 14p 

594LABEL_OFFSET = 0.1125i 

595OBLIQUE_ANNOTATION = 1 

596PLOT_CLOCK_FORMAT = hh:mm:ss 

597PLOT_DATE_FORMAT = yyyy-mm-dd 

598PLOT_DEGREE_FORMAT = +ddd:mm:ss 

599Y_AXIS_TYPE = hor_text 

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

601BASEMAP_AXES = WESN 

602BASEMAP_FRAME_RGB = 0/0/0 

603BASEMAP_TYPE = plain 

604FRAME_PEN = 1.25p 

605FRAME_WIDTH = 0.075i 

606GRID_CROSS_SIZE_PRIMARY = 0i 

607GRID_PEN_PRIMARY = 0.25p 

608GRID_CROSS_SIZE_SECONDARY = 0i 

609GRID_PEN_SECONDARY = 0.5p 

610MAP_SCALE_HEIGHT = 0.075i 

611POLAR_CAP = 85/90 

612TICK_LENGTH = 0.075i 

613TICK_PEN = 0.5p 

614X_AXIS_LENGTH = 9i 

615Y_AXIS_LENGTH = 6i 

616X_ORIGIN = 1i 

617Y_ORIGIN = 1i 

618UNIX_TIME = FALSE 

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

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

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

622COLOR_BACKGROUND = 0/0/0 

623COLOR_FOREGROUND = 255/255/255 

624COLOR_NAN = 128/128/128 

625COLOR_IMAGE = adobe 

626COLOR_MODEL = rgb 

627HSV_MIN_SATURATION = 1 

628HSV_MAX_SATURATION = 0.1 

629HSV_MIN_VALUE = 0.3 

630HSV_MAX_VALUE = 1 

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

632CHAR_ENCODING = ISOLatin1+ 

633DOTS_PR_INCH = 300 

634N_COPIES = 1 

635PS_COLOR = rgb 

636PS_IMAGE_COMPRESS = none 

637PS_IMAGE_FORMAT = ascii 

638PS_LINE_CAP = round 

639PS_LINE_JOIN = miter 

640PS_MITER_LIMIT = 35 

641PS_VERBOSE = FALSE 

642GLOBAL_X_SCALE = 1 

643GLOBAL_Y_SCALE = 1 

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

645D_FORMAT = %lg 

646FIELD_DELIMITER = tab 

647GRIDFILE_SHORTHAND = FALSE 

648GRID_FORMAT = nf 

649INPUT_CLOCK_FORMAT = hh:mm:ss 

650INPUT_DATE_FORMAT = yyyy-mm-dd 

651IO_HEADER = FALSE 

652N_HEADER_RECS = 1 

653OUTPUT_CLOCK_FORMAT = hh:mm:ss 

654OUTPUT_DATE_FORMAT = yyyy-mm-dd 

655OUTPUT_DEGREE_FORMAT = +D 

656XY_TOGGLE = FALSE 

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

658ELLIPSOID = WGS-84 

659MAP_SCALE_FACTOR = default 

660MEASURE_UNIT = inch 

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

662TIME_FORMAT_PRIMARY = full 

663TIME_FORMAT_SECONDARY = full 

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

665TIME_IS_INTERVAL = OFF 

666TIME_INTERVAL_FRACTION = 0.5 

667TIME_LANGUAGE = us 

668TIME_UNIT = d 

669TIME_WEEK_START = Sunday 

670Y2K_OFFSET_YEAR = 1950 

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

672HISTORY = TRUE 

673INTERPOLANT = akima 

674LINE_STEP = 0.01i 

675VECTOR_SHAPE = 0 

676VERBOSE = FALSE''' 

677 

678 

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

680# 

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

682# 

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

684PAGE_COLOR = 255/255/255 

685PAGE_ORIENTATION = portrait 

686PAPER_MEDIA = a4+ 

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

688ANNOT_MIN_ANGLE = 20 

689ANNOT_MIN_SPACING = 0 

690ANNOT_FONT_PRIMARY = Helvetica 

691ANNOT_FONT_SIZE_PRIMARY = 14p 

692ANNOT_OFFSET_PRIMARY = 0.075i 

693ANNOT_FONT_SECONDARY = Helvetica 

694ANNOT_FONT_SIZE_SECONDARY = 16p 

695ANNOT_OFFSET_SECONDARY = 0.075i 

696DEGREE_SYMBOL = ring 

697HEADER_FONT = Helvetica 

698HEADER_FONT_SIZE = 36p 

699HEADER_OFFSET = 0.1875i 

700LABEL_FONT = Helvetica 

701LABEL_FONT_SIZE = 14p 

702LABEL_OFFSET = 0.1125i 

703OBLIQUE_ANNOTATION = 1 

704PLOT_CLOCK_FORMAT = hh:mm:ss 

705PLOT_DATE_FORMAT = yyyy-mm-dd 

706PLOT_DEGREE_FORMAT = +ddd:mm:ss 

707Y_AXIS_TYPE = hor_text 

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

709BASEMAP_AXES = WESN 

710BASEMAP_FRAME_RGB = 0/0/0 

711BASEMAP_TYPE = plain 

712FRAME_PEN = 1.25p 

713FRAME_WIDTH = 0.075i 

714GRID_CROSS_SIZE_PRIMARY = 0i 

715GRID_PEN_PRIMARY = 0.25p 

716GRID_CROSS_SIZE_SECONDARY = 0i 

717GRID_PEN_SECONDARY = 0.5p 

718MAP_SCALE_HEIGHT = 0.075i 

719POLAR_CAP = 85/90 

720TICK_LENGTH = 0.075i 

721TICK_PEN = 0.5p 

722X_AXIS_LENGTH = 9i 

723Y_AXIS_LENGTH = 6i 

724X_ORIGIN = 1i 

725Y_ORIGIN = 1i 

726UNIX_TIME = FALSE 

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

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

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

730COLOR_BACKGROUND = 0/0/0 

731COLOR_FOREGROUND = 255/255/255 

732COLOR_NAN = 128/128/128 

733COLOR_IMAGE = adobe 

734COLOR_MODEL = rgb 

735HSV_MIN_SATURATION = 1 

736HSV_MAX_SATURATION = 0.1 

737HSV_MIN_VALUE = 0.3 

738HSV_MAX_VALUE = 1 

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

740CHAR_ENCODING = ISOLatin1+ 

741DOTS_PR_INCH = 300 

742N_COPIES = 1 

743PS_COLOR = rgb 

744PS_IMAGE_COMPRESS = lzw 

745PS_IMAGE_FORMAT = ascii 

746PS_LINE_CAP = round 

747PS_LINE_JOIN = miter 

748PS_MITER_LIMIT = 35 

749PS_VERBOSE = FALSE 

750GLOBAL_X_SCALE = 1 

751GLOBAL_Y_SCALE = 1 

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

753D_FORMAT = %lg 

754FIELD_DELIMITER = tab 

755GRIDFILE_SHORTHAND = FALSE 

756GRID_FORMAT = nf 

757INPUT_CLOCK_FORMAT = hh:mm:ss 

758INPUT_DATE_FORMAT = yyyy-mm-dd 

759IO_HEADER = FALSE 

760N_HEADER_RECS = 1 

761OUTPUT_CLOCK_FORMAT = hh:mm:ss 

762OUTPUT_DATE_FORMAT = yyyy-mm-dd 

763OUTPUT_DEGREE_FORMAT = +D 

764XY_TOGGLE = FALSE 

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

766ELLIPSOID = WGS-84 

767MAP_SCALE_FACTOR = default 

768MEASURE_UNIT = inch 

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

770TIME_FORMAT_PRIMARY = full 

771TIME_FORMAT_SECONDARY = full 

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

773TIME_IS_INTERVAL = OFF 

774TIME_INTERVAL_FRACTION = 0.5 

775TIME_LANGUAGE = us 

776TIME_UNIT = d 

777TIME_WEEK_START = Sunday 

778Y2K_OFFSET_YEAR = 1950 

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

780HISTORY = TRUE 

781INTERPOLANT = akima 

782LINE_STEP = 0.01i 

783VECTOR_SHAPE = 0 

784VERBOSE = FALSE 

785''' 

786 

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

788# 

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

790# 

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

792PAGE_COLOR = white 

793PAGE_ORIENTATION = portrait 

794PAPER_MEDIA = a4+ 

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

796ANNOT_MIN_ANGLE = 20 

797ANNOT_MIN_SPACING = 0 

798ANNOT_FONT_PRIMARY = Helvetica 

799ANNOT_FONT_SIZE_PRIMARY = 14p 

800ANNOT_OFFSET_PRIMARY = 0.075i 

801ANNOT_FONT_SECONDARY = Helvetica 

802ANNOT_FONT_SIZE_SECONDARY = 16p 

803ANNOT_OFFSET_SECONDARY = 0.075i 

804DEGREE_SYMBOL = ring 

805HEADER_FONT = Helvetica 

806HEADER_FONT_SIZE = 36p 

807HEADER_OFFSET = 0.1875i 

808LABEL_FONT = Helvetica 

809LABEL_FONT_SIZE = 14p 

810LABEL_OFFSET = 0.1125i 

811OBLIQUE_ANNOTATION = 1 

812PLOT_CLOCK_FORMAT = hh:mm:ss 

813PLOT_DATE_FORMAT = yyyy-mm-dd 

814PLOT_DEGREE_FORMAT = +ddd:mm:ss 

815Y_AXIS_TYPE = hor_text 

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

817BASEMAP_AXES = WESN 

818BASEMAP_FRAME_RGB = black 

819BASEMAP_TYPE = plain 

820FRAME_PEN = 1.25p 

821FRAME_WIDTH = 0.075i 

822GRID_CROSS_SIZE_PRIMARY = 0i 

823GRID_PEN_PRIMARY = 0.25p 

824GRID_CROSS_SIZE_SECONDARY = 0i 

825GRID_PEN_SECONDARY = 0.5p 

826MAP_SCALE_HEIGHT = 0.075i 

827POLAR_CAP = 85/90 

828TICK_LENGTH = 0.075i 

829TICK_PEN = 0.5p 

830X_AXIS_LENGTH = 9i 

831Y_AXIS_LENGTH = 6i 

832X_ORIGIN = 1i 

833Y_ORIGIN = 1i 

834UNIX_TIME = FALSE 

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

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

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

838COLOR_BACKGROUND = black 

839COLOR_FOREGROUND = white 

840COLOR_NAN = 128 

841COLOR_IMAGE = adobe 

842COLOR_MODEL = rgb 

843HSV_MIN_SATURATION = 1 

844HSV_MAX_SATURATION = 0.1 

845HSV_MIN_VALUE = 0.3 

846HSV_MAX_VALUE = 1 

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

848CHAR_ENCODING = ISOLatin1+ 

849DOTS_PR_INCH = 300 

850GLOBAL_X_SCALE = 1 

851GLOBAL_Y_SCALE = 1 

852N_COPIES = 1 

853PS_COLOR = rgb 

854PS_IMAGE_COMPRESS = lzw 

855PS_IMAGE_FORMAT = ascii 

856PS_LINE_CAP = round 

857PS_LINE_JOIN = miter 

858PS_MITER_LIMIT = 35 

859PS_VERBOSE = FALSE 

860TRANSPARENCY = 0 

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

862D_FORMAT = %.12lg 

863FIELD_DELIMITER = tab 

864GRIDFILE_FORMAT = nf 

865GRIDFILE_SHORTHAND = FALSE 

866INPUT_CLOCK_FORMAT = hh:mm:ss 

867INPUT_DATE_FORMAT = yyyy-mm-dd 

868IO_HEADER = FALSE 

869N_HEADER_RECS = 1 

870NAN_RECORDS = pass 

871OUTPUT_CLOCK_FORMAT = hh:mm:ss 

872OUTPUT_DATE_FORMAT = yyyy-mm-dd 

873OUTPUT_DEGREE_FORMAT = D 

874XY_TOGGLE = FALSE 

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

876ELLIPSOID = WGS-84 

877MAP_SCALE_FACTOR = default 

878MEASURE_UNIT = inch 

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

880TIME_FORMAT_PRIMARY = full 

881TIME_FORMAT_SECONDARY = full 

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

883TIME_IS_INTERVAL = OFF 

884TIME_INTERVAL_FRACTION = 0.5 

885TIME_LANGUAGE = us 

886TIME_UNIT = d 

887TIME_WEEK_START = Sunday 

888Y2K_OFFSET_YEAR = 1950 

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

890HISTORY = TRUE 

891INTERPOLANT = akima 

892LINE_STEP = 0.01i 

893VECTOR_SHAPE = 0 

894VERBOSE = FALSE 

895''' 

896 

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

898# 

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

900# 

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

902PAGE_COLOR = white 

903PAGE_ORIENTATION = portrait 

904PAPER_MEDIA = a4+ 

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

906ANNOT_MIN_ANGLE = 20 

907ANNOT_MIN_SPACING = 0 

908ANNOT_FONT_PRIMARY = Helvetica 

909ANNOT_FONT_SIZE_PRIMARY = 14p 

910ANNOT_OFFSET_PRIMARY = 0.075i 

911ANNOT_FONT_SECONDARY = Helvetica 

912ANNOT_FONT_SIZE_SECONDARY = 16p 

913ANNOT_OFFSET_SECONDARY = 0.075i 

914DEGREE_SYMBOL = ring 

915HEADER_FONT = Helvetica 

916HEADER_FONT_SIZE = 36p 

917HEADER_OFFSET = 0.1875i 

918LABEL_FONT = Helvetica 

919LABEL_FONT_SIZE = 14p 

920LABEL_OFFSET = 0.1125i 

921OBLIQUE_ANNOTATION = 1 

922PLOT_CLOCK_FORMAT = hh:mm:ss 

923PLOT_DATE_FORMAT = yyyy-mm-dd 

924PLOT_DEGREE_FORMAT = +ddd:mm:ss 

925Y_AXIS_TYPE = hor_text 

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

927BASEMAP_AXES = WESN 

928BASEMAP_FRAME_RGB = black 

929BASEMAP_TYPE = plain 

930FRAME_PEN = 1.25p 

931FRAME_WIDTH = 0.075i 

932GRID_CROSS_SIZE_PRIMARY = 0i 

933GRID_PEN_PRIMARY = 0.25p 

934GRID_CROSS_SIZE_SECONDARY = 0i 

935GRID_PEN_SECONDARY = 0.5p 

936MAP_SCALE_HEIGHT = 0.075i 

937POLAR_CAP = 85/90 

938TICK_LENGTH = 0.075i 

939TICK_PEN = 0.5p 

940X_AXIS_LENGTH = 9i 

941Y_AXIS_LENGTH = 6i 

942X_ORIGIN = 1i 

943Y_ORIGIN = 1i 

944UNIX_TIME = FALSE 

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

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

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

948COLOR_BACKGROUND = black 

949COLOR_FOREGROUND = white 

950COLOR_NAN = 128 

951COLOR_IMAGE = adobe 

952COLOR_MODEL = rgb 

953HSV_MIN_SATURATION = 1 

954HSV_MAX_SATURATION = 0.1 

955HSV_MIN_VALUE = 0.3 

956HSV_MAX_VALUE = 1 

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

958CHAR_ENCODING = ISOLatin1+ 

959DOTS_PR_INCH = 300 

960GLOBAL_X_SCALE = 1 

961GLOBAL_Y_SCALE = 1 

962N_COPIES = 1 

963PS_COLOR = rgb 

964PS_IMAGE_COMPRESS = lzw 

965PS_IMAGE_FORMAT = ascii 

966PS_LINE_CAP = round 

967PS_LINE_JOIN = miter 

968PS_MITER_LIMIT = 35 

969PS_VERBOSE = FALSE 

970TRANSPARENCY = 0 

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

972D_FORMAT = %.12lg 

973FIELD_DELIMITER = tab 

974GRIDFILE_FORMAT = nf 

975GRIDFILE_SHORTHAND = FALSE 

976INPUT_CLOCK_FORMAT = hh:mm:ss 

977INPUT_DATE_FORMAT = yyyy-mm-dd 

978IO_HEADER = FALSE 

979N_HEADER_RECS = 1 

980NAN_RECORDS = pass 

981OUTPUT_CLOCK_FORMAT = hh:mm:ss 

982OUTPUT_DATE_FORMAT = yyyy-mm-dd 

983OUTPUT_DEGREE_FORMAT = D 

984XY_TOGGLE = FALSE 

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

986ELLIPSOID = WGS-84 

987MAP_SCALE_FACTOR = default 

988MEASURE_UNIT = inch 

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

990TIME_FORMAT_PRIMARY = full 

991TIME_FORMAT_SECONDARY = full 

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

993TIME_IS_INTERVAL = OFF 

994TIME_INTERVAL_FRACTION = 0.5 

995TIME_LANGUAGE = us 

996TIME_UNIT = d 

997TIME_WEEK_START = Sunday 

998Y2K_OFFSET_YEAR = 1950 

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

1000HISTORY = TRUE 

1001INTERPOLANT = akima 

1002LINE_STEP = 0.01i 

1003VECTOR_SHAPE = 0 

1004VERBOSE = FALSE 

1005''' 

1006 

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

1008# 

1009# GMT 5.1.2 Defaults file 

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

1011# $Revision: 13836 $ 

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

1013# 

1014# COLOR Parameters 

1015# 

1016COLOR_BACKGROUND = black 

1017COLOR_FOREGROUND = white 

1018COLOR_NAN = 127.5 

1019COLOR_MODEL = none 

1020COLOR_HSV_MIN_S = 1 

1021COLOR_HSV_MAX_S = 0.1 

1022COLOR_HSV_MIN_V = 0.3 

1023COLOR_HSV_MAX_V = 1 

1024# 

1025# DIR Parameters 

1026# 

1027DIR_DATA = 

1028DIR_DCW = 

1029DIR_GSHHG = 

1030# 

1031# FONT Parameters 

1032# 

1033FONT_ANNOT_PRIMARY = 14p,Helvetica,black 

1034FONT_ANNOT_SECONDARY = 16p,Helvetica,black 

1035FONT_LABEL = 14p,Helvetica,black 

1036FONT_LOGO = 8p,Helvetica,black 

1037FONT_TITLE = 24p,Helvetica,black 

1038# 

1039# FORMAT Parameters 

1040# 

1041FORMAT_CLOCK_IN = hh:mm:ss 

1042FORMAT_CLOCK_OUT = hh:mm:ss 

1043FORMAT_CLOCK_MAP = hh:mm:ss 

1044FORMAT_DATE_IN = yyyy-mm-dd 

1045FORMAT_DATE_OUT = yyyy-mm-dd 

1046FORMAT_DATE_MAP = yyyy-mm-dd 

1047FORMAT_GEO_OUT = D 

1048FORMAT_GEO_MAP = ddd:mm:ss 

1049FORMAT_FLOAT_OUT = %.12g 

1050FORMAT_FLOAT_MAP = %.12g 

1051FORMAT_TIME_PRIMARY_MAP = full 

1052FORMAT_TIME_SECONDARY_MAP = full 

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

1054# 

1055# GMT Miscellaneous Parameters 

1056# 

1057GMT_COMPATIBILITY = 4 

1058GMT_CUSTOM_LIBS = 

1059GMT_EXTRAPOLATE_VAL = NaN 

1060GMT_FFT = auto 

1061GMT_HISTORY = true 

1062GMT_INTERPOLANT = akima 

1063GMT_TRIANGULATE = Shewchuk 

1064GMT_VERBOSE = compat 

1065GMT_LANGUAGE = us 

1066# 

1067# I/O Parameters 

1068# 

1069IO_COL_SEPARATOR = tab 

1070IO_GRIDFILE_FORMAT = nf 

1071IO_GRIDFILE_SHORTHAND = false 

1072IO_HEADER = false 

1073IO_N_HEADER_RECS = 0 

1074IO_NAN_RECORDS = pass 

1075IO_NC4_CHUNK_SIZE = auto 

1076IO_NC4_DEFLATION_LEVEL = 3 

1077IO_LONLAT_TOGGLE = false 

1078IO_SEGMENT_MARKER = > 

1079# 

1080# MAP Parameters 

1081# 

1082MAP_ANNOT_MIN_ANGLE = 20 

1083MAP_ANNOT_MIN_SPACING = 0p 

1084MAP_ANNOT_OBLIQUE = 1 

1085MAP_ANNOT_OFFSET_PRIMARY = 0.075i 

1086MAP_ANNOT_OFFSET_SECONDARY = 0.075i 

1087MAP_ANNOT_ORTHO = we 

1088MAP_DEFAULT_PEN = default,black 

1089MAP_DEGREE_SYMBOL = ring 

1090MAP_FRAME_AXES = WESNZ 

1091MAP_FRAME_PEN = thicker,black 

1092MAP_FRAME_TYPE = fancy 

1093MAP_FRAME_WIDTH = 5p 

1094MAP_GRID_CROSS_SIZE_PRIMARY = 0p 

1095MAP_GRID_CROSS_SIZE_SECONDARY = 0p 

1096MAP_GRID_PEN_PRIMARY = default,black 

1097MAP_GRID_PEN_SECONDARY = thinner,black 

1098MAP_LABEL_OFFSET = 0.1944i 

1099MAP_LINE_STEP = 0.75p 

1100MAP_LOGO = false 

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

1102MAP_ORIGIN_X = 1i 

1103MAP_ORIGIN_Y = 1i 

1104MAP_POLAR_CAP = 85/90 

1105MAP_SCALE_HEIGHT = 5p 

1106MAP_TICK_LENGTH_PRIMARY = 5p/2.5p 

1107MAP_TICK_LENGTH_SECONDARY = 15p/3.75p 

1108MAP_TICK_PEN_PRIMARY = thinner,black 

1109MAP_TICK_PEN_SECONDARY = thinner,black 

1110MAP_TITLE_OFFSET = 14p 

1111MAP_VECTOR_SHAPE = 0 

1112# 

1113# Projection Parameters 

1114# 

1115PROJ_AUX_LATITUDE = authalic 

1116PROJ_ELLIPSOID = WGS-84 

1117PROJ_LENGTH_UNIT = cm 

1118PROJ_MEAN_RADIUS = authalic 

1119PROJ_SCALE_FACTOR = default 

1120# 

1121# PostScript Parameters 

1122# 

1123PS_CHAR_ENCODING = ISOLatin1+ 

1124PS_COLOR_MODEL = rgb 

1125PS_COMMENTS = false 

1126PS_IMAGE_COMPRESS = deflate,5 

1127PS_LINE_CAP = butt 

1128PS_LINE_JOIN = miter 

1129PS_MITER_LIMIT = 35 

1130PS_MEDIA = a4 

1131PS_PAGE_COLOR = white 

1132PS_PAGE_ORIENTATION = portrait 

1133PS_SCALE_X = 1 

1134PS_SCALE_Y = 1 

1135PS_TRANSPARENCY = Normal 

1136# 

1137# Calendar/Time Parameters 

1138# 

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

1140TIME_IS_INTERVAL = off 

1141TIME_INTERVAL_FRACTION = 0.5 

1142TIME_UNIT = s 

1143TIME_WEEK_START = Monday 

1144TIME_Y2K_OFFSET_YEAR = 1950 

1145''' 

1146 

1147 

1148def get_gmt_version(gmtdefaultsbinary, gmthomedir=None): 

1149 args = [gmtdefaultsbinary] 

1150 

1151 environ = os.environ.copy() 

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

1153 

1154 p = subprocess.Popen( 

1155 args, 

1156 stdout=subprocess.PIPE, 

1157 stderr=subprocess.PIPE, 

1158 env=environ) 

1159 

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

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

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

1163 

1164 if not m: 

1165 raise GMTInstallationProblem( 

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

1167 % gmtdefaultsbinary) 

1168 

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

1170 

1171 

1172def detect_gmt_installations(): 

1173 

1174 installations = {} 

1175 errmesses = [] 

1176 

1177 # GMT 4.x: 

1178 try: 

1179 p = subprocess.Popen( 

1180 ['GMT'], 

1181 stdout=subprocess.PIPE, 

1182 stderr=subprocess.PIPE) 

1183 

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

1185 

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

1187 if not m: 

1188 raise GMTInstallationProblem( 

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

1190 

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

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

1193 

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

1195 if not m: 

1196 raise GMTInstallationProblem( 

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

1198 

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

1200 

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

1202 if not m: 

1203 raise GMTInstallationProblem( 

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

1205 

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

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

1208 raise GMTInstallationProblem( 

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

1210 

1211 gmthome = gmtshare[:-6] 

1212 

1213 installations[version] = { 

1214 'home': gmthome, 

1215 'bin': gmtbin} 

1216 

1217 except OSError as e: 

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

1219 

1220 try: 

1221 version = str(subprocess.check_output( 

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

1223 gmtbin = str(subprocess.check_output( 

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

1225 installations[version] = { 

1226 'bin': gmtbin} 

1227 

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

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

1230 

1231 if not installations: 

1232 s = [] 

1233 for (progname, errmess) in errmesses: 

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

1235 

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

1237 

1238 return installations 

1239 

1240 

1241def appropriate_defaults_version(version): 

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

1243 for iavail, avail in enumerate(avails): 

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

1245 return version 

1246 

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

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

1249 

1250 return avails[-1] 

1251 

1252 

1253def gmt_default_config(version): 

1254 ''' 

1255 Get default GMT configuration dict for given version. 

1256 ''' 

1257 

1258 xversion = appropriate_defaults_version(version) 

1259 

1260 # if not version in _gmt_defaults_by_version: 

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

1262 

1263 gmt_defaults = _gmt_defaults_by_version[xversion] 

1264 

1265 d = {} 

1266 for line in gmt_defaults.splitlines(): 

1267 sline = line.strip() 

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

1269 continue 

1270 

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

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

1273 

1274 return d 

1275 

1276 

1277def diff_defaults(v1, v2): 

1278 d1 = gmt_default_config(v1) 

1279 d2 = gmt_default_config(v2) 

1280 for k in d1: 

1281 if k not in d2: 

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

1283 else: 

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

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

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

1287 

1288 for k in d2: 

1289 if k not in d1: 

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

1291 

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

1293 

1294 

1295def check_gmt_installation(installation): 

1296 

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

1298 bin_dir = installation['bin'] 

1299 version = installation['version'] 

1300 

1301 for d in home_dir, bin_dir: 

1302 if d is not None: 

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

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

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

1306 

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

1308 

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

1310 gmtdefaults = pjoin(bin_dir, 'gmtdefaults') 

1311 

1312 versionfound = get_gmt_version(gmtdefaults, home_dir) 

1313 

1314 if versionfound != version: 

1315 raise GMTInstallationProblem(( 

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

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

1318 version, versionfound, gmtdefaults)) 

1319 

1320 

1321def get_gmt_installation(version): 

1322 setup_gmt_installations() 

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

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

1325 % (version, newest_installed_gmt_version())) 

1326 

1327 version = 'newest' 

1328 

1329 if version == 'newest': 

1330 version = newest_installed_gmt_version() 

1331 

1332 installation = dict(_gmt_installations[version]) 

1333 

1334 return installation 

1335 

1336 

1337def setup_gmt_installations(): 

1338 if not setup_gmt_installations.have_done: 

1339 if not _gmt_installations: 

1340 

1341 _gmt_installations.update(detect_gmt_installations()) 

1342 

1343 # store defaults as dicts into the gmt installations dicts 

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

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

1346 installation['version'] = version 

1347 

1348 for installation in _gmt_installations.values(): 

1349 check_gmt_installation(installation) 

1350 

1351 setup_gmt_installations.have_done = True 

1352 

1353 

1354setup_gmt_installations.have_done = False 

1355 

1356_paper_sizes_a = '''A0 2380 3368 

1357 A1 1684 2380 

1358 A2 1190 1684 

1359 A3 842 1190 

1360 A4 595 842 

1361 A5 421 595 

1362 A6 297 421 

1363 A7 210 297 

1364 A8 148 210 

1365 A9 105 148 

1366 A10 74 105 

1367 B0 2836 4008 

1368 B1 2004 2836 

1369 B2 1418 2004 

1370 B3 1002 1418 

1371 B4 709 1002 

1372 B5 501 709 

1373 archA 648 864 

1374 archB 864 1296 

1375 archC 1296 1728 

1376 archD 1728 2592 

1377 archE 2592 3456 

1378 flsa 612 936 

1379 halfletter 396 612 

1380 note 540 720 

1381 letter 612 792 

1382 legal 612 1008 

1383 11x17 792 1224 

1384 ledger 1224 792''' 

1385 

1386 

1387_paper_sizes = {} 

1388 

1389 

1390def setup_paper_sizes(): 

1391 if not _paper_sizes: 

1392 for line in _paper_sizes_a.splitlines(): 

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

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

1395 

1396 

1397def get_paper_size(k): 

1398 setup_paper_sizes() 

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

1400 

1401 

1402def all_paper_sizes(): 

1403 setup_paper_sizes() 

1404 return _paper_sizes 

1405 

1406 

1407def measure_unit(gmt_config): 

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

1409 if k in gmt_config: 

1410 return gmt_config[k] 

1411 

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

1413 

1414 

1415def paper_media(gmt_config): 

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

1417 if k in gmt_config: 

1418 return gmt_config[k] 

1419 

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

1421 

1422 

1423def page_orientation(gmt_config): 

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

1425 if k in gmt_config: 

1426 return gmt_config[k] 

1427 

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

1429 

1430 

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

1432 

1433 leftmargin, topmargin, rightmargin, bottommargin = margins 

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

1435 

1436 paper_size = get_paper_size(paper_media(gmt_config)) 

1437 if not portrait: 

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

1439 

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

1441 2.0 + leftmargin 

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

1443 2.0 + bottommargin 

1444 

1445 if portrait: 

1446 bb1 = int((xoffset - leftmargin)) 

1447 bb2 = int((yoffset - bottommargin)) 

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

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

1450 else: 

1451 bb1 = int((yoffset - topmargin)) 

1452 bb2 = int((xoffset - leftmargin)) 

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

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

1455 

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

1457 

1458 

1459def gmtdefaults_as_text(version='newest'): 

1460 

1461 ''' 

1462 Get the built-in gmtdefaults. 

1463 ''' 

1464 

1465 if version not in _gmt_installations: 

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

1467 % (version, newest_installed_gmt_version())) 

1468 version = 'newest' 

1469 

1470 if version == 'newest': 

1471 version = newest_installed_gmt_version() 

1472 

1473 return _gmt_defaults_by_version[version] 

1474 

1475 

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

1477 ''' 

1478 Write COARDS compliant netcdf (grd) file. 

1479 ''' 

1480 

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

1482 ny, nx = z.shape 

1483 nc = netcdf_file(filename, 'w') 

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

1485 

1486 if naming == 'xy': 

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

1488 else: 

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

1490 

1491 nc.node_offset = 0 

1492 if title is not None: 

1493 nc.title = title 

1494 

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

1496 nc.createDimension(kx, nx) 

1497 nc.createDimension(ky, ny) 

1498 

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

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

1501 if naming == 'xy': 

1502 xvar.long_name = kx 

1503 yvar.long_name = ky 

1504 else: 

1505 xvar.long_name = 'longitude' 

1506 xvar.units = 'degrees_east' 

1507 yvar.long_name = 'latitude' 

1508 yvar.units = 'degrees_north' 

1509 

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

1511 

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

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

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

1515 

1516 nc.close() 

1517 

1518 

1519def to_array(var): 

1520 arr = var[:].copy() 

1521 if hasattr(var, 'scale_factor'): 

1522 arr *= var.scale_factor 

1523 

1524 if hasattr(var, 'add_offset'): 

1525 arr += var.add_offset 

1526 

1527 return arr 

1528 

1529 

1530def loadgrd(filename): 

1531 ''' 

1532 Read COARDS compliant netcdf (grd) file. 

1533 ''' 

1534 

1535 nc = netcdf_file(filename, 'r') 

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

1537 kx = 'x' 

1538 ky = 'y' 

1539 if 'lon' in vkeys: 

1540 kx = 'lon' 

1541 if 'lat' in vkeys: 

1542 ky = 'lat' 

1543 

1544 kz = 'z' 

1545 if 'altitude' in vkeys: 

1546 kz = 'altitude' 

1547 

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

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

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

1551 

1552 nc.close() 

1553 return x, y, z 

1554 

1555 

1556def centers_to_edges(asorted): 

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

1558 

1559 

1560def nvals(asorted): 

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

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

1563 

1564 

1565def guess_vals(asorted): 

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

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

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

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

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

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

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

1573 

1574 

1575def blockmean(asorted, b): 

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

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

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

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

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

1581 return ( 

1582 asorted[indis[:-1]], 

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

1584 

1585 

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

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

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

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

1590 

1591 zindi = yindi*nx+xindi 

1592 order = num.argsort(zindi) 

1593 z = z[order] 

1594 zindi = zindi[order] 

1595 

1596 zindi, z = blockmean(zindi, z) 

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

1598 znew[:] = num.nan 

1599 znew[zindi] = z 

1600 return znew.reshape(ny, nx) 

1601 

1602 

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

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

1605 xs = x_sorted 

1606 ys = y_sorted 

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

1608 if mode == 'nonrandom': 

1609 return nxs, nys, 0 

1610 elif xs.size == nxs*nys: 

1611 # exact match 

1612 return nxs, nys, 0 

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

1614 # possibly randomly sampled 

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

1616 nys = nxs 

1617 return nxs, nys, 2 

1618 else: 

1619 return nxs, nys, 1 

1620 

1621 

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

1623 ''' 

1624 Grid tabular XYZ data by binning. 

1625 

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

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

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

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

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

1631 

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

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

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

1635 ''' 

1636 

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

1638 assert x.size == y.size == z.size 

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

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

1641 if badness <= 1: 

1642 xf = guess_vals(xs) 

1643 yf = guess_vals(ys) 

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

1645 else: 

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

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

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

1649 

1650 return xf, yf, zf 

1651 

1652 

1653def tabledata(xf, yf, zf): 

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

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

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

1657 z = zf.flatten() 

1658 return x, y, z 

1659 

1660 

1661def double1d(a): 

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

1663 a2[::2] = a 

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

1665 return a2 

1666 

1667 

1668def double2d(f): 

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

1670 f2[:, :] = num.nan 

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

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

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

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

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

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

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

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

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

1680 return f2 

1681 

1682 

1683def doublegrid(x, y, z): 

1684 x2 = double1d(x) 

1685 y2 = double1d(y) 

1686 z2 = double2d(z) 

1687 return x2, y2, z2 

1688 

1689 

1690class Guru(object): 

1691 ''' 

1692 Abstract base class providing template interpolation, accessible as 

1693 attributes. 

1694 

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

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

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

1698 with the templates. 

1699 ''' 

1700 

1701 def __init__(self): 

1702 self.templates = {} 

1703 

1704 def get_params(self, ax_projection=False): 

1705 ''' 

1706 To be implemented in subclasses. 

1707 ''' 

1708 raise NotImplementedError 

1709 

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

1711 params = self.get_params(**kwargs) 

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

1713 return strings 

1714 

1715 # hand through templates dict 

1716 def __getitem__(self, template_name): 

1717 return self.templates[template_name] 

1718 

1719 def __setitem__(self, template_name, template): 

1720 self.templates[template_name] = template 

1721 

1722 def __contains__(self, template_name): 

1723 return template_name in self.templates 

1724 

1725 def __iter__(self): 

1726 return iter(self.templates) 

1727 

1728 def __len__(self): 

1729 return len(self.templates) 

1730 

1731 def __delitem__(self, template_name): 

1732 del self.templates[template_name] 

1733 

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

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

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

1737 

1738 def __getattr__(self, template_names): 

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

1740 raise AttributeError(template_names) 

1741 

1742 def f(**kwargs): 

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

1744 

1745 return f 

1746 

1747 

1748class Ax(AutoScaler): 

1749 ''' 

1750 Ax description with autoscaling capabilities. 

1751 

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

1753 public attributes, plus the following additional attributes 

1754 (with default values given in paranthesis): 

1755 

1756 .. py:attribute:: label 

1757 

1758 Ax label (without unit). 

1759 

1760 .. py:attribute:: unit 

1761 

1762 Physical unit of the data attached to this ax. 

1763 

1764 .. py:attribute:: scaled_unit 

1765 

1766 (see below) 

1767 

1768 .. py:attribute:: scaled_unit_factor 

1769 

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

1771 

1772 unit = scaled_unit_factor x scaled_unit. 

1773 

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

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

1776 1e9.) 

1777 

1778 .. py:attribute:: limits 

1779 

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

1781 

1782 .. py:attribute:: masking 

1783 

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

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

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

1787 

1788 ''' 

1789 

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

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

1792 

1793 AutoScaler.__init__(self, **kwargs) 

1794 self.label = label 

1795 self.unit = unit 

1796 self.scaled_unit_factor = scaled_unit_factor 

1797 self.scaled_unit = scaled_unit 

1798 self.limits = limits 

1799 self.masking = masking 

1800 

1801 def label_str(self, exp, unit): 

1802 ''' 

1803 Get label string including the unit and multiplier. 

1804 ''' 

1805 

1806 slabel, sunit, sexp = '', '', '' 

1807 if self.label: 

1808 slabel = self.label 

1809 

1810 if unit or exp != 0: 

1811 if exp != 0: 

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

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

1814 else: 

1815 sunit = '[ %s ]' % unit 

1816 

1817 p = [] 

1818 if slabel: 

1819 p.append(slabel) 

1820 

1821 if sunit: 

1822 p.append(sunit) 

1823 

1824 return ' '.join(p) 

1825 

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

1827 override_scaled_unit_factor=None): 

1828 

1829 ''' 

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

1831 

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

1833 multiplier for given data range. 

1834 

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

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

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

1838 scaling applied. 

1839 ''' 

1840 

1841 sf = self.scaled_unit_factor 

1842 

1843 if override_scaled_unit_factor is not None: 

1844 sf = override_scaled_unit_factor 

1845 

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

1847 

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

1849 if self.inc is not None: 

1850 inc = self.inc*sf 

1851 

1852 if ax_projection: 

1853 exp = self.make_exp(inc) 

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

1855 unit = self.unit 

1856 else: 

1857 unit = self.scaled_unit 

1858 label = self.label_str(exp, unit) 

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

1860 else: 

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

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

1863 

1864 

1865class ScaleGuru(Guru): 

1866 

1867 ''' 

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

1869 

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

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

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

1873 arguments, which are required for most GMT commands. 

1874 

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

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

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

1878 e.g.: 

1879 

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

1881 

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

1883 limits imposed on other axes. 

1884 

1885 ''' 

1886 

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

1888 percent_interval=None, copy_from=None): 

1889 

1890 Guru.__init__(self) 

1891 

1892 if copy_from: 

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

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

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

1896 self.aspect = copy_from.aspect 

1897 

1898 if percent_interval is not None: 

1899 from scipy.stats import scoreatpercentile as scap 

1900 

1901 self.templates = dict( 

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

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

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

1905 

1906 maxdim = 2 

1907 if data_tuples: 

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

1909 else: 

1910 if axes: 

1911 maxdim = len(axes) 

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

1913 if axes is not None: 

1914 self.axes = axes 

1915 else: 

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

1917 

1918 # sophisticated data-range calculation 

1919 data_ranges = [None] * maxdim 

1920 for dt_ in data_tuples: 

1921 dt = num.asarray(dt_) 

1922 in_range = True 

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

1924 if ax.limits and ax.masking: 

1925 ax_limits = list(ax.limits) 

1926 if ax_limits[0] is None: 

1927 ax_limits[0] = -num.inf 

1928 if ax_limits[1] is None: 

1929 ax_limits[1] = num.inf 

1930 in_range = num.logical_and( 

1931 in_range, 

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

1933 

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

1935 

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

1937 if len(x) >= 1: 

1938 if in_range is not True: 

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

1940 if percent_interval is None: 

1941 range_this = ( 

1942 num.nanmin(xmasked), 

1943 num.nanmax(xmasked)) 

1944 else: 

1945 xmasked_finite = num.compress( 

1946 num.isfinite(xmasked), xmasked) 

1947 range_this = ( 

1948 scap(xmasked_finite, 

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

1950 scap(xmasked_finite, 

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

1952 else: 

1953 if percent_interval is None: 

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

1955 else: 

1956 xmasked_finite = num.compress( 

1957 num.isfinite(xmasked), xmasked) 

1958 range_this = ( 

1959 scap(xmasked_finite, 

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

1961 scap(xmasked_finite, 

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

1963 else: 

1964 range_this = (0., 1.) 

1965 

1966 if ax.limits: 

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

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

1969 range_this[1]) 

1970 

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

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

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

1974 

1975 else: 

1976 range_this = ax.limits 

1977 

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

1979 data_ranges[i] = range_this 

1980 else: 

1981 mi, ma = range_this 

1982 if data_ranges[i] is not None: 

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

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

1985 

1986 data_ranges[i] = (mi, ma) 

1987 

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

1989 if data_ranges[i] is None or not ( 

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

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

1992 

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

1994 

1995 self.data_ranges = data_ranges 

1996 self.aspect = aspect 

1997 

1998 def copy(self): 

1999 return ScaleGuru(copy_from=self) 

2000 

2001 def get_params(self, ax_projection=False): 

2002 

2003 ''' 

2004 Get dict with output parameters. 

2005 

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

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

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

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

2010 

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

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

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

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

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

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

2017 label string. 

2018 ''' 

2019 

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

2021 self.data_ranges[0], ax_projection) 

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

2023 self.data_ranges[1], ax_projection) 

2024 if len(self.axes) > 2: 

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

2026 self.data_ranges[2], ax_projection) 

2027 

2028 # enforce certain aspect, if needed 

2029 if self.aspect is not None: 

2030 xwid = xma-xmi 

2031 ywid = yma-ymi 

2032 if ywid < xwid*self.aspect: 

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

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

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

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

2037 override_scaled_unit_factor=1.) 

2038 

2039 elif xwid < ywid/self.aspect: 

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

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

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

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

2044 override_scaled_unit_factor=1.) 

2045 

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

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

2048 if len(self.axes) > 2: 

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

2050 

2051 return params 

2052 

2053 

2054class GumSpring(object): 

2055 

2056 ''' 

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

2058 ''' 

2059 

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

2061 self.minimal = minimal 

2062 if grow is None: 

2063 if minimal is None: 

2064 self.grow = 1.0 

2065 else: 

2066 self.grow = 0.0 

2067 else: 

2068 self.grow = grow 

2069 self.value = 1.0 

2070 

2071 def get_minimal(self): 

2072 if self.minimal is not None: 

2073 return self.minimal 

2074 else: 

2075 return 0.0 

2076 

2077 def get_grow(self): 

2078 return self.grow 

2079 

2080 def set_value(self, value): 

2081 self.value = value 

2082 

2083 def get_value(self): 

2084 return self.value 

2085 

2086 

2087def distribute(sizes, grows, space): 

2088 sizes = list(sizes) 

2089 gsum = sum(grows) 

2090 if gsum > 0.0: 

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

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

2093 return sizes 

2094 

2095 

2096class Widget(Guru): 

2097 

2098 ''' 

2099 Base class of the gmtpy layout system. 

2100 

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

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

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

2104 

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

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

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

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

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

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

2111 

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

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

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

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

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

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

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

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

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

2121 

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

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

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

2125 ''' 

2126 

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

2128 

2129 ''' 

2130 Create new widget. 

2131 ''' 

2132 

2133 Guru.__init__(self) 

2134 

2135 self.templates = dict( 

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

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

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

2139 

2140 if horizontal is None: 

2141 self.horizontal = GumSpring() 

2142 else: 

2143 self.horizontal = horizontal 

2144 

2145 if vertical is None: 

2146 self.vertical = GumSpring() 

2147 else: 

2148 self.vertical = vertical 

2149 

2150 self.aspect = None 

2151 self.parent = parent 

2152 self.dirty = True 

2153 

2154 def set_widget(self): 

2155 ''' 

2156 To be implemented in subclasses. 

2157 ''' 

2158 raise NotImplementedError 

2159 

2160 def set_parent(self, parent): 

2161 

2162 ''' 

2163 Set the parent widget. 

2164 

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

2166 methods are responsible for calling this. 

2167 ''' 

2168 

2169 self.parent = parent 

2170 self.dirtyfy() 

2171 

2172 def get_parent(self): 

2173 

2174 ''' 

2175 Get the widgets parent widget. 

2176 ''' 

2177 

2178 return self.parent 

2179 

2180 def get_root(self): 

2181 

2182 ''' 

2183 Get the root widget in the layout hierarchy. 

2184 ''' 

2185 

2186 if self.parent is not None: 

2187 return self.get_parent() 

2188 else: 

2189 return self 

2190 

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

2192 

2193 ''' 

2194 Set the horizontal sizing policy of the Widget. 

2195 

2196 

2197 :param minimal: new minimal width of the widget 

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

2199 ''' 

2200 

2201 self.horizontal = GumSpring(minimal, grow) 

2202 self.dirtyfy() 

2203 

2204 def get_horizontal(self): 

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

2206 

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

2208 

2209 ''' 

2210 Set the horizontal sizing policy of the Widget. 

2211 

2212 :param minimal: new minimal height of the widget 

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

2214 ''' 

2215 

2216 self.vertical = GumSpring(minimal, grow) 

2217 self.dirtyfy() 

2218 

2219 def get_vertical(self): 

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

2221 

2222 def set_aspect(self, aspect=None): 

2223 

2224 ''' 

2225 Set aspect constraint on the widget. 

2226 

2227 The aspect is given as height divided by width. 

2228 ''' 

2229 

2230 self.aspect = aspect 

2231 self.dirtyfy() 

2232 

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

2234 

2235 ''' 

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

2237 call. 

2238 ''' 

2239 

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

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

2242 self.set_aspect(aspect) 

2243 

2244 def get_policy(self): 

2245 mh, gh = self.get_horizontal() 

2246 mv, gv = self.get_vertical() 

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

2248 

2249 def legalize(self, size, offset): 

2250 

2251 ''' 

2252 Get legal size for widget. 

2253 

2254 Returns: (new_size, new_offset) 

2255 

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

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

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

2259 ''' 

2260 

2261 sh, sv = size 

2262 oh, ov = offset 

2263 shs, svs = Widget.get_min_size(self) 

2264 ghs, gvs = Widget.get_grow(self) 

2265 

2266 if ghs == 0.0: 

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

2268 sh = shs 

2269 

2270 if gvs == 0.0: 

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

2272 sv = svs 

2273 

2274 if self.aspect is not None: 

2275 if sh > sv/self.aspect: 

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

2277 sh = sv/self.aspect 

2278 if sv > sh*self.aspect: 

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

2280 sv = sh*self.aspect 

2281 

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

2283 

2284 def get_min_size(self): 

2285 

2286 ''' 

2287 Get minimum size of widget. 

2288 

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

2290 ''' 

2291 

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

2293 if self.aspect is not None: 

2294 if mv == 0.0: 

2295 return mh, mh*self.aspect 

2296 elif mh == 0.0: 

2297 return mv/self.aspect, mv 

2298 return mh, mv 

2299 

2300 def get_grow(self): 

2301 

2302 ''' 

2303 Get widget's desire to grow. 

2304 

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

2306 ''' 

2307 

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

2309 

2310 def set_size(self, size, offset): 

2311 

2312 ''' 

2313 Set the widget's current size. 

2314 

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

2316 responsibility to call this. 

2317 ''' 

2318 

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

2320 self.offset = inner_offset 

2321 self.horizontal.set_value(sh) 

2322 self.vertical.set_value(sv) 

2323 self.dirty = False 

2324 

2325 def __str__(self): 

2326 

2327 def indent(ind, str): 

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

2329 size, offset = self.get_size() 

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

2331 children = self.get_children() 

2332 if children: 

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

2334 return s 

2335 

2336 def policies_debug_str(self): 

2337 

2338 def indent(ind, str): 

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

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

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

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

2343 

2344 children = self.get_children() 

2345 if children: 

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

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

2348 return s 

2349 

2350 def get_corners(self, descend=False): 

2351 

2352 ''' 

2353 Get coordinates of the corners of the widget. 

2354 

2355 Returns list with coordinate tuples. 

2356 

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

2358 coordinates of all sub-widgets. 

2359 ''' 

2360 

2361 self.do_layout() 

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

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

2364 if descend: 

2365 for child in self.get_children(): 

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

2367 return corners 

2368 

2369 def get_sizes(self): 

2370 

2371 ''' 

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

2373 

2374 Returns a list with size tuples. 

2375 ''' 

2376 self.do_layout() 

2377 sizes = [self.get_size()] 

2378 for child in self.get_children(): 

2379 sizes.extend(child.get_sizes()) 

2380 return sizes 

2381 

2382 def do_layout(self): 

2383 

2384 ''' 

2385 Triggers layouting of the widget hierarchy, if needed. 

2386 ''' 

2387 

2388 if self.parent is not None: 

2389 return self.parent.do_layout() 

2390 

2391 if not self.dirty: 

2392 return 

2393 

2394 sh, sv = self.get_min_size() 

2395 gh, gv = self.get_grow() 

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

2397 sh = 15.*cm 

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

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

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

2401 

2402 def get_children(self): 

2403 

2404 ''' 

2405 Get sub-widgets contained in this widget. 

2406 

2407 Returns a list of widgets. 

2408 ''' 

2409 

2410 return [] 

2411 

2412 def get_size(self): 

2413 

2414 ''' 

2415 Get current size and position of the widget. 

2416 

2417 Triggers layouting and returns 

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

2419 ''' 

2420 

2421 self.do_layout() 

2422 return (self.horizontal.get_value(), 

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

2424 

2425 def get_params(self): 

2426 

2427 ''' 

2428 Get current size and position of the widget. 

2429 

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

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

2432 ''' 

2433 

2434 self.do_layout() 

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

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

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

2438 

2439 def width(self): 

2440 

2441 ''' 

2442 Get current width of the widget. 

2443 

2444 Triggers layouting and returns width. 

2445 ''' 

2446 

2447 self.do_layout() 

2448 return self.horizontal.get_value() 

2449 

2450 def height(self): 

2451 

2452 ''' 

2453 Get current height of the widget. 

2454 

2455 Triggers layouting and return height. 

2456 ''' 

2457 

2458 self.do_layout() 

2459 return self.vertical.get_value() 

2460 

2461 def bbox(self): 

2462 

2463 ''' 

2464 Get PostScript bounding box for this widget. 

2465 

2466 Triggers layouting and returns values suitable to create PS bounding 

2467 box, representing the widgets current size and position. 

2468 ''' 

2469 

2470 self.do_layout() 

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

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

2473 

2474 def dirtyfy(self): 

2475 

2476 ''' 

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

2478 

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

2480 new layouting. 

2481 ''' 

2482 

2483 if self.parent is not None: 

2484 self.parent.dirtyfy() 

2485 

2486 self.dirty = True 

2487 

2488 

2489class CenterLayout(Widget): 

2490 

2491 ''' 

2492 A layout manager which centers its single child widget. 

2493 

2494 The child widget may be oversized. 

2495 ''' 

2496 

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

2498 Widget.__init__(self, horizontal, vertical) 

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

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

2501 

2502 def get_min_size(self): 

2503 shs, svs = Widget.get_min_size(self) 

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

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

2506 

2507 def get_grow(self): 

2508 ghs, gvs = Widget.get_grow(self) 

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

2510 return gh*ghs, gv*gvs 

2511 

2512 def set_size(self, size, offset): 

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

2514 

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

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

2517 if ghc != 0.: 

2518 shc = sh 

2519 if gvc != 0.: 

2520 svc = sv 

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

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

2523 

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

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

2526 

2527 def set_widget(self, widget=None): 

2528 

2529 ''' 

2530 Set the child widget, which shall be centered. 

2531 ''' 

2532 

2533 if widget is None: 

2534 widget = Widget() 

2535 

2536 self.content = widget 

2537 

2538 widget.set_parent(self) 

2539 

2540 def get_widget(self): 

2541 return self.content 

2542 

2543 def get_children(self): 

2544 return [self.content] 

2545 

2546 

2547class FrameLayout(Widget): 

2548 

2549 ''' 

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

2551 widgets. 

2552 

2553 :: 

2554 

2555 +---------------------------+ 

2556 | top | 

2557 +---------------------------+ 

2558 | | | | 

2559 | left | center | right | 

2560 | | | | 

2561 +---------------------------+ 

2562 | bottom | 

2563 +---------------------------+ 

2564 

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

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

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

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

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

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

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

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

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

2574 spaces between the widgets. 

2575 ''' 

2576 

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

2578 Widget.__init__(self, horizontal, vertical) 

2579 mw = 3.*cm 

2580 self.left = Widget( 

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

2582 self.right = Widget( 

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

2584 self.top = Widget( 

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

2586 parent=self) 

2587 self.bottom = Widget( 

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

2589 parent=self) 

2590 self.center = Widget( 

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

2592 parent=self) 

2593 

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

2595 ''' 

2596 Give margins fixed size constraints. 

2597 ''' 

2598 

2599 self.left.set_horizontal(left, 0) 

2600 self.right.set_horizontal(right, 0) 

2601 self.top.set_vertical(top, 0) 

2602 self.bottom.set_vertical(bottom, 0) 

2603 

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

2605 ''' 

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

2607 

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

2609 ''' 

2610 self.left.set_horizontal(left, grow) 

2611 self.right.set_horizontal(right, grow) 

2612 self.top.set_vertical(top, grow) 

2613 self.bottom.set_vertical(bottom, grow) 

2614 

2615 def get_min_size(self): 

2616 shs, svs = Widget.get_min_size(self) 

2617 

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

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

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

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

2622 

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

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

2625 

2626 # prevent widgets from collapsing 

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

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

2629 shsum += 0.1*cm 

2630 

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

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

2633 svsum += 0.1*cm 

2634 

2635 sh = max(shs, shsum) 

2636 sv = max(svs, svsum) 

2637 

2638 return sh, sv 

2639 

2640 def get_grow(self): 

2641 ghs, gvs = Widget.get_grow(self) 

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

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

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

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

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

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

2648 return gh, gv 

2649 

2650 def set_size(self, size, offset): 

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

2652 

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

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

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

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

2657 

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

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

2660 

2661 if ah < 0.0: 

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

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

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

2665 if av < 0.0: 

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

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

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

2669 

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

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

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

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

2674 

2675 if self.center.aspect is not None: 

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

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

2678 if 0.0 < ahm < ah: 

2679 slh, srh, sch = distribute( 

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

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

2682 

2683 elif 0.0 < avm < av: 

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

2685 sch*self.center.aspect), 

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

2687 

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

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

2690 

2691 oh += ah/2. 

2692 ov += av/2. 

2693 sh -= ah 

2694 sv -= av 

2695 

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

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

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

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

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

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

2702 

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

2704 

2705 ''' 

2706 Set one of the sub-widgets. 

2707 

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

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

2710 ''' 

2711 

2712 if widget is None: 

2713 widget = Widget() 

2714 

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

2716 self.__dict__[which] = widget 

2717 else: 

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

2719 

2720 widget.set_parent(self) 

2721 

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

2723 

2724 ''' 

2725 Get one of the sub-widgets. 

2726 

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

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

2729 ''' 

2730 

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

2732 return self.__dict__[which] 

2733 else: 

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

2735 

2736 def get_children(self): 

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

2738 

2739 

2740class GridLayout(Widget): 

2741 

2742 ''' 

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

2744 

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

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

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

2748 

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

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

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

2752 might not be resolved optimally. 

2753 ''' 

2754 

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

2756 

2757 ''' 

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

2759 ''' 

2760 

2761 Widget.__init__(self, horizontal, vertical) 

2762 self.grid = [] 

2763 for iy in range(ny): 

2764 row = [] 

2765 for ix in range(nx): 

2766 w = Widget(parent=self) 

2767 row.append(w) 

2768 

2769 self.grid.append(row) 

2770 

2771 def sub_min_sizes_as_array(self): 

2772 esh = num.array( 

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

2774 dtype=float) 

2775 esv = num.array( 

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

2777 dtype=float) 

2778 return esh, esv 

2779 

2780 def sub_grows_as_array(self): 

2781 egh = num.array( 

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

2783 dtype=float) 

2784 egv = num.array( 

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

2786 dtype=float) 

2787 return egh, egv 

2788 

2789 def get_min_size(self): 

2790 sh, sv = Widget.get_min_size(self) 

2791 esh, esv = self.sub_min_sizes_as_array() 

2792 if esh.size != 0: 

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

2794 if esv.size != 0: 

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

2796 return sh, sv 

2797 

2798 def get_grow(self): 

2799 ghs, gvs = Widget.get_grow(self) 

2800 egh, egv = self.sub_grows_as_array() 

2801 if egh.size != 0: 

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

2803 else: 

2804 gh = 1.0 

2805 if egv.size != 0: 

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

2807 else: 

2808 gv = 1.0 

2809 return gh, gv 

2810 

2811 def set_size(self, size, offset): 

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

2813 esh, esv = self.sub_min_sizes_as_array() 

2814 egh, egv = self.sub_grows_as_array() 

2815 

2816 # available additional space 

2817 empty = esh.size == 0 

2818 

2819 if not empty: 

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

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

2822 else: 

2823 av = sv 

2824 ah = sh 

2825 

2826 if ah < 0.0: 

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

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

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

2830 if av < 0.0: 

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

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

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

2834 

2835 nx, ny = esh.shape 

2836 

2837 if not empty: 

2838 # distribute additional space on rows and columns 

2839 # according to grow weights and minimal sizes 

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

2841 nesh = esh.copy() 

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

2843 

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

2845 

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

2847 nesv = esv.copy() 

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

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

2850 

2851 ah = sh - sum(nsh) 

2852 av = sv - sum(nsv) 

2853 

2854 oh += ah/2. 

2855 ov += av/2. 

2856 sh -= ah 

2857 sv -= av 

2858 

2859 # resize child widgets 

2860 neov = ov + sum(nsv) 

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

2862 neov -= nesv 

2863 neoh = oh 

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

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

2866 neoh += nesh 

2867 

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

2869 

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

2871 

2872 ''' 

2873 Set one of the sub-widgets. 

2874 

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

2876 counted from zero. 

2877 ''' 

2878 

2879 if widget is None: 

2880 widget = Widget() 

2881 

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

2883 widget.set_parent(self) 

2884 

2885 def get_widget(self, ix, iy): 

2886 

2887 ''' 

2888 Get one of the sub-widgets. 

2889 

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

2891 counted from zero. 

2892 ''' 

2893 

2894 return self.grid[iy][ix] 

2895 

2896 def get_children(self): 

2897 children = [] 

2898 for row in self.grid: 

2899 children.extend(row) 

2900 

2901 return children 

2902 

2903 

2904def is_gmt5(version='newest'): 

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

2906 

2907 

2908def is_gmt6(version='newest'): 

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

2910 

2911 

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

2913 

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

2915 

2916 if gmt.is_gmt5(): 

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

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

2919 gmt.save(fn, crop_eps_mode=True) 

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

2921 s = f.read() 

2922 

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

2924 else: 

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

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

2927 

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

2929 

2930 

2931def text_box( 

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

2933 

2934 gmt = GMT(version=gmtversion) 

2935 if gmt.is_gmt5(): 

2936 row = [0, 0, text] 

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

2938 else: 

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

2940 farg = [] 

2941 

2942 gmt.pstext( 

2943 in_rows=[row], 

2944 finish=True, 

2945 R=(0, 1, 0, 1), 

2946 J='x10p', 

2947 N=True, 

2948 *farg, 

2949 **kwargs) 

2950 

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

2952 gmt.save(fn) 

2953 

2954 (_, stderr) = subprocess.Popen( 

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

2956 stderr=subprocess.PIPE).communicate() 

2957 

2958 dx, dy = None, None 

2959 for line in stderr.splitlines(): 

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

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

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

2963 break 

2964 

2965 return dx, dy 

2966 

2967 

2968class TableLiner(object): 

2969 ''' 

2970 Utility class to turn tables into lines. 

2971 ''' 

2972 

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

2974 self.in_columns = in_columns 

2975 self.in_rows = in_rows 

2976 self.encoding = encoding 

2977 

2978 def __iter__(self): 

2979 if self.in_columns is not None: 

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

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

2982 self.encoding) 

2983 

2984 if self.in_rows is not None: 

2985 for row in self.in_rows: 

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

2987 self.encoding) 

2988 

2989 

2990class LineStreamChopper(object): 

2991 ''' 

2992 File-like object to buffer data. 

2993 ''' 

2994 

2995 def __init__(self, liner): 

2996 self.chopsize = None 

2997 self.liner = liner 

2998 self.chop_iterator = None 

2999 self.closed = False 

3000 

3001 def _chopiter(self): 

3002 buf = BytesIO() 

3003 for line in self.liner: 

3004 buf.write(line) 

3005 buflen = buf.tell() 

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

3007 buf.seek(0) 

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

3009 yield buf.read(self.chopsize) 

3010 

3011 newbuf = BytesIO() 

3012 newbuf.write(buf.read()) 

3013 buf.close() 

3014 buf = newbuf 

3015 

3016 yield buf.getvalue() 

3017 buf.close() 

3018 

3019 def read(self, size=None): 

3020 if self.closed: 

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

3022 if self.chop_iterator is None: 

3023 self.chopsize = size 

3024 self.chop_iterator = self._chopiter() 

3025 

3026 self.chopsize = size 

3027 try: 

3028 return next(self.chop_iterator) 

3029 except StopIteration: 

3030 return '' 

3031 

3032 def close(self): 

3033 self.chopsize = None 

3034 self.chop_iterator = None 

3035 self.closed = True 

3036 

3037 def flush(self): 

3038 pass 

3039 

3040 

3041font_tab = { 

3042 0: 'Helvetica', 

3043 1: 'Helvetica-Bold', 

3044} 

3045 

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

3047 

3048 

3049class GMT(object): 

3050 ''' 

3051 A thin wrapper to GMT command execution. 

3052 

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

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

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

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

3057 gmtpy and gmtpy must know where to find it. 

3058 

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

3060 output file. 

3061 

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

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

3064 

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

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

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

3068 

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

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

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

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

3073 

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

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

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

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

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

3079 execution of more than one GMT instance. 

3080 

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

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

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

3084 backward compatibility of the scripts can be maintained. 

3085 

3086 ''' 

3087 

3088 def __init__( 

3089 self, 

3090 config=None, 

3091 kontinue=None, 

3092 version='newest', 

3093 config_papersize=None, 

3094 eps_mode=False): 

3095 

3096 self.installation = get_gmt_installation(version) 

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

3098 self.eps_mode = eps_mode 

3099 self._shutil = shutil 

3100 

3101 if config: 

3102 self.gmt_config.update(config) 

3103 

3104 if config_papersize: 

3105 if not isinstance(config_papersize, str): 

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

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

3108 

3109 if self.is_gmt5(): 

3110 self.gmt_config['PS_MEDIA'] = config_papersize 

3111 else: 

3112 self.gmt_config['PAPER_MEDIA'] = config_papersize 

3113 

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

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

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

3117 

3118 if kontinue is not None: 

3119 self.load_unfinished(kontinue) 

3120 self.needstart = False 

3121 else: 

3122 self.output = BytesIO() 

3123 self.needstart = True 

3124 

3125 self.finished = False 

3126 

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

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

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

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

3131 

3132 self.layout = None 

3133 self.command_log = [] 

3134 self.keep_temp_dir = False 

3135 

3136 def is_gmt5(self): 

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

3138 

3139 def is_gmt6(self): 

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

3141 

3142 def get_version(self): 

3143 return self.installation['version'] 

3144 

3145 def get_config(self, key): 

3146 return self.gmt_config[key] 

3147 

3148 def to_points(self, string): 

3149 if not string: 

3150 return 0 

3151 

3152 unit = string[-1] 

3153 if unit in _units: 

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

3155 else: 

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

3157 return float(string)/_units[default_unit] 

3158 

3159 def label_font_size(self): 

3160 if self.is_gmt5(): 

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

3162 else: 

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

3164 

3165 def label_font(self): 

3166 if self.is_gmt5(): 

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

3168 else: 

3169 return self.gmt_config['LABEL_FONT'] 

3170 

3171 def gen_gmt_config_file(self, config_filename, config): 

3172 f = open(config_filename, 'wb') 

3173 f.write( 

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

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

3176 

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

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

3179 f.close() 

3180 

3181 def __del__(self): 

3182 if not self.keep_temp_dir: 

3183 self._shutil.rmtree(self.tempdir) 

3184 

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

3186 

3187 ''' 

3188 Execute arbitrary GMT command. 

3189 

3190 See docstring in __getattr__ for details. 

3191 ''' 

3192 

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

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

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

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

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

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

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

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

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

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

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

3204 

3205 assert not self.finished 

3206 

3207 # check for mutual exclusiveness on input and output possibilities 

3208 assert (1 >= len( 

3209 [x for x in [ 

3210 in_stream, in_filename, in_string, in_columns, in_rows] 

3211 if x is not None])) 

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

3213 if x is not None])) 

3214 

3215 options = [] 

3216 

3217 gmt_config = self.gmt_config 

3218 if not self.is_gmt5(): 

3219 gmt_config_filename = self.gmt_config_filename 

3220 if config_override: 

3221 gmt_config = self.gmt_config.copy() 

3222 gmt_config.update(config_override) 

3223 gmt_config_override_filename = pjoin( 

3224 self.tempdir, 'gmtdefaults_override') 

3225 self.gen_gmt_config_file( 

3226 gmt_config_override_filename, gmt_config) 

3227 gmt_config_filename = gmt_config_override_filename 

3228 

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

3230 if config_override: 

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

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

3233 

3234 if out_discard: 

3235 out_filename = '/dev/null' 

3236 

3237 out_mustclose = False 

3238 if out_filename is not None: 

3239 out_mustclose = True 

3240 out_stream = open(out_filename, 'wb') 

3241 

3242 if in_filename is not None: 

3243 in_stream = open(in_filename, 'rb') 

3244 

3245 if in_string is not None: 

3246 in_stream = BytesIO(in_string) 

3247 

3248 encoding_gmt = gmt_config.get( 

3249 'PS_CHAR_ENCODING', 

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

3251 

3252 encoding = encoding_gmt_to_python[encoding_gmt.lower()] 

3253 

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

3255 in_stream = LineStreamChopper(TableLiner(in_columns=in_columns, 

3256 in_rows=in_rows, 

3257 encoding=encoding)) 

3258 

3259 # convert option arguments to strings 

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

3261 if len(k) > 1: 

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

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

3264 % (k, command)) 

3265 

3266 if type(v) is bool: 

3267 if v: 

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

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

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

3271 else: 

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

3273 

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

3275 if out_stream is None: 

3276 if not finish: 

3277 options.append('-K') 

3278 else: 

3279 self.finished = True 

3280 

3281 if not self.needstart: 

3282 options.append('-O') 

3283 else: 

3284 self.needstart = False 

3285 

3286 out_stream = self.output 

3287 

3288 # run the command 

3289 if self.is_gmt5(): 

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

3291 else: 

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

3293 

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

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

3296 args.extend(options) 

3297 args.extend(addargs) 

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

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

3300 args.append('+'+gmt_config_filename) 

3301 

3302 bs = 2048 

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

3304 stdout=subprocess.PIPE, bufsize=bs, 

3305 env=self.environ) 

3306 while True: 

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

3308 if cr: 

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

3310 if cw: 

3311 if in_stream is not None: 

3312 data = in_stream.read(bs) 

3313 if len(data) == 0: 

3314 break 

3315 p.stdin.write(data) 

3316 else: 

3317 break 

3318 if not cr and not cw: 

3319 break 

3320 

3321 p.stdin.close() 

3322 

3323 while True: 

3324 data = p.stdout.read(bs) 

3325 if len(data) == 0: 

3326 break 

3327 out_stream.write(data) 

3328 

3329 p.stdout.close() 

3330 

3331 retcode = p.wait() 

3332 

3333 if in_stream is not None: 

3334 in_stream.close() 

3335 

3336 if out_mustclose: 

3337 out_stream.close() 

3338 

3339 if retcode != 0: 

3340 self.keep_temp_dir = True 

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

3342 'While executing command:\n%s' 

3343 % (command, escape_shell_args(args))) 

3344 

3345 self.command_log.append(args) 

3346 

3347 def __getattr__(self, command): 

3348 

3349 ''' 

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

3351 

3352 Execute arbitrary GMT command. 

3353 

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

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

3356 called. 

3357 

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

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

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

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

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

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

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

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

3366 

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

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

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

3370 not interested in the output. 

3371 

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

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

3374 

3375 =============== ======================================================= 

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

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

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

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

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

3381 ascii 

3382 table, which is fed to the process. 

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

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

3385 table, which is fed to the process. 

3386 =============== ======================================================= 

3387 

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

3389 following options: 

3390 

3391 ================= ===================================================== 

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

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

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

3395 ================= ===================================================== 

3396 

3397 Additional keyword arguments: 

3398 

3399 ===================== ================================================= 

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

3401 currently active set of defaults exclusively 

3402 during this call. 

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

3404 by the GMT instance is finished, and no further 

3405 plotting is allowed. 

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

3407 option to the command. 

3408 ===================== ================================================= 

3409 

3410 ''' 

3411 

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

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

3414 return f 

3415 

3416 def tempfilename(self, name=None): 

3417 ''' 

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

3419 

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

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

3422 ''' 

3423 

3424 if not name: 

3425 name = ''.join( 

3426 [random.choice('abcdefghijklmnopqrstuvwxyz') 

3427 for i in range(10)]) 

3428 

3429 fn = pjoin(self.tempdir, name) 

3430 return fn 

3431 

3432 def tempfile(self, name=None): 

3433 ''' 

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

3435 ''' 

3436 

3437 fn = self.tempfilename(name) 

3438 f = open(fn, 'wb') 

3439 return f, fn 

3440 

3441 def save_unfinished(self, filename): 

3442 out = open(filename, 'wb') 

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

3444 out.close() 

3445 

3446 def load_unfinished(self, filename): 

3447 self.output = BytesIO() 

3448 self.finished = False 

3449 inp = open(filename, 'rb') 

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

3451 inp.close() 

3452 

3453 def dump(self, ident): 

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

3455 self.save_unfinished(filename) 

3456 

3457 def load(self, ident): 

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

3459 self.load_unfinished(filename) 

3460 

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

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

3463 psconvert=False): 

3464 

3465 ''' 

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

3467 

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

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

3470 

3471 If filename ends with ``'.png'`` a PNG file is created by running 

3472 :program:`gmtpy-epstopdf`, :program:`pdftocairo` and 

3473 :program:`convert`. ``resolution`` specifies the resolution in DPI for 

3474 raster file formats. Rasterization is done at a higher resolution if 

3475 ``oversample`` is set to a value higher than one. The output image size 

3476 can also be controlled by setting ``width``, ``height`` or ``size`` 

3477 instead of ``resolution``. When ``size`` is given, the image is scaled 

3478 so that ``max(width, height) == size``. 

3479 

3480 The bounding box is set according to the values given in ``bbox``. 

3481 ''' 

3482 

3483 if not self.finished: 

3484 self.psxy(R=True, J=True, finish=True) 

3485 

3486 if filename: 

3487 tempfn = pjoin(self.tempdir, 'incomplete') 

3488 out = open(tempfn, 'wb') 

3489 else: 

3490 out = sys.stdout 

3491 

3492 if bbox and not self.is_gmt5(): 

3493 out.write(replace_bbox(bbox, self.output.getvalue())) 

3494 else: 

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

3496 

3497 if filename: 

3498 out.close() 

3499 

3500 if filename.endswith('.ps') or ( 

3501 not self.is_gmt5() and filename.endswith('.eps')): 

3502 

3503 shutil.move(tempfn, filename) 

3504 return 

3505 

3506 if self.is_gmt5(): 

3507 if crop_eps_mode: 

3508 addarg = ['-A'] 

3509 else: 

3510 addarg = [] 

3511 

3512 subprocess.call( 

3513 [pjoin(self.installation['bin'], 'gmt'), 'psconvert', 

3514 '-Te', '-F%s' % tempfn, tempfn, ] + addarg) 

3515 

3516 if bbox: 

3517 with open(tempfn + '.eps', 'rb') as fin: 

3518 with open(tempfn + '-fixbb.eps', 'wb') as fout: 

3519 replace_bbox(bbox, fin, fout) 

3520 

3521 shutil.move(tempfn + '-fixbb.eps', tempfn + '.eps') 

3522 

3523 else: 

3524 shutil.move(tempfn, tempfn + '.eps') 

3525 

3526 if filename.endswith('.eps'): 

3527 shutil.move(tempfn + '.eps', filename) 

3528 return 

3529 

3530 elif filename.endswith('.pdf'): 

3531 if psconvert: 

3532 gmt_bin = pjoin(self.installation['bin'], 'gmt') 

3533 subprocess.call([gmt_bin, 'psconvert', tempfn + '.eps', '-Tf', 

3534 '-F' + filename]) 

3535 else: 

3536 subprocess.call(['gmtpy-epstopdf', '--res=%i' % resolution, 

3537 '--outfile=' + filename, tempfn + '.eps']) 

3538 else: 

3539 subprocess.call([ 

3540 'gmtpy-epstopdf', 

3541 '--res=%i' % (resolution * oversample), 

3542 '--outfile=' + tempfn + '.pdf', tempfn + '.eps']) 

3543 

3544 convert_graph( 

3545 tempfn + '.pdf', filename, 

3546 resolution=resolution, oversample=oversample, 

3547 size=size, width=width, height=height) 

3548 

3549 def bbox(self): 

3550 return get_bbox(self.output.getvalue()) 

3551 

3552 def get_command_log(self): 

3553 ''' 

3554 Get the command log. 

3555 ''' 

3556 

3557 return self.command_log 

3558 

3559 def __str__(self): 

3560 s = '' 

3561 for com in self.command_log: 

3562 s += com[0] + '\n ' + '\n '.join(com[1:]) + '\n\n' 

3563 return s 

3564 

3565 def page_size_points(self): 

3566 ''' 

3567 Try to get paper size of output postscript file in points. 

3568 ''' 

3569 

3570 pm = paper_media(self.gmt_config).lower() 

3571 if pm.endswith('+') or pm.endswith('-'): 

3572 pm = pm[:-1] 

3573 

3574 orient = page_orientation(self.gmt_config).lower() 

3575 

3576 if pm in all_paper_sizes(): 

3577 

3578 if orient == 'portrait': 

3579 return get_paper_size(pm) 

3580 else: 

3581 return get_paper_size(pm)[1], get_paper_size(pm)[0] 

3582 

3583 m = re.match(r'custom_([0-9.]+)([cimp]?)x([0-9.]+)([cimp]?)', pm) 

3584 if m: 

3585 w, uw, h, uh = m.groups() 

3586 w, h = float(w), float(h) 

3587 if uw: 

3588 w *= _units[uw] 

3589 if uh: 

3590 h *= _units[uh] 

3591 if orient == 'portrait': 

3592 return w, h 

3593 else: 

3594 return h, w 

3595 

3596 return None, None 

3597 

3598 def default_layout(self, with_palette=False): 

3599 ''' 

3600 Get a default layout for the output page. 

3601 

3602 One of three different layouts is choosen, depending on the 

3603 `PAPER_MEDIA` setting in the GMT configuration dict. 

3604 

3605 If `PAPER_MEDIA` ends with a ``'+'`` (EPS output is selected), a 

3606 :py:class:`FrameLayout` is centered on the page, whose size is 

3607 controlled by its center widget's size plus the margins of the 

3608 :py:class:`FrameLayout`. 

3609 

3610 If `PAPER_MEDIA` indicates, that a custom page size is wanted by 

3611 starting with ``'Custom_'``, a :py:class:`FrameLayout` is used to fill 

3612 the complete page. The center widget's size is then controlled by the 

3613 page's size minus the margins of the :py:class:`FrameLayout`. 

3614 

3615 In any other case, two FrameLayouts are nested, such that the outer 

3616 layout attaches a 1 cm (printer) margin around the complete page, and 

3617 the inner FrameLayout's center widget takes up as much space as 

3618 possible under the constraint, that an aspect ratio of 1/golden_ratio 

3619 is preserved. 

3620 

3621 In any case, a reference to the innermost :py:class:`FrameLayout` 

3622 instance is returned. The top-level layout can be accessed by calling 

3623 :py:meth:`Widget.get_parent` on the returned layout. 

3624 ''' 

3625 

3626 if self.layout is None: 

3627 w, h = self.page_size_points() 

3628 

3629 if w is None or h is None: 

3630 raise GmtPyError("Can't determine page size for layout") 

3631 

3632 pm = paper_media(self.gmt_config).lower() 

3633 

3634 if with_palette: 

3635 palette_layout = GridLayout(3, 1) 

3636 spacer = palette_layout.get_widget(1, 0) 

3637 palette_widget = palette_layout.get_widget(2, 0) 

3638 spacer.set_horizontal(0.5*cm) 

3639 palette_widget.set_horizontal(0.5*cm) 

3640 

3641 if pm.endswith('+') or self.eps_mode: 

3642 outer = CenterLayout() 

3643 outer.set_policy((w, h), (0., 0.)) 

3644 inner = FrameLayout() 

3645 outer.set_widget(inner) 

3646 if with_palette: 

3647 inner.set_widget('center', palette_layout) 

3648 widget = palette_layout 

3649 else: 

3650 widget = inner.get_widget('center') 

3651 widget.set_policy((w/golden_ratio, 0.), (0., 0.), 

3652 aspect=1./golden_ratio) 

3653 mw = 3.0*cm 

3654 inner.set_fixed_margins( 

3655 mw, mw, mw/golden_ratio, mw/golden_ratio) 

3656 self.layout = inner 

3657 

3658 elif pm.startswith('custom_'): 

3659 layout = FrameLayout() 

3660 layout.set_policy((w, h), (0., 0.)) 

3661 mw = 3.0*cm 

3662 layout.set_min_margins( 

3663 mw, mw, mw/golden_ratio, mw/golden_ratio) 

3664 if with_palette: 

3665 layout.set_widget('center', palette_layout) 

3666 self.layout = layout 

3667 else: 

3668 outer = FrameLayout() 

3669 outer.set_policy((w, h), (0., 0.)) 

3670 outer.set_fixed_margins(1.*cm, 1.*cm, 1.*cm, 1.*cm) 

3671 

3672 inner = FrameLayout() 

3673 outer.set_widget('center', inner) 

3674 mw = 3.0*cm 

3675 inner.set_min_margins(mw, mw, mw/golden_ratio, mw/golden_ratio) 

3676 if with_palette: 

3677 inner.set_widget('center', palette_layout) 

3678 widget = palette_layout 

3679 else: 

3680 widget = inner.get_widget('center') 

3681 

3682 widget.set_aspect(1./golden_ratio) 

3683 

3684 self.layout = inner 

3685 

3686 return self.layout 

3687 

3688 def draw_layout(self, layout): 

3689 ''' 

3690 Use psxy to draw layout; for debugging 

3691 ''' 

3692 

3693 # corners = layout.get_corners(descend=True) 

3694 rects = num.array(layout.get_sizes(), dtype=float) 

3695 rects_wid = rects[:, 0, 0] 

3696 rects_hei = rects[:, 0, 1] 

3697 rects_center_x = rects[:, 1, 0] + rects_wid*0.5 

3698 rects_center_y = rects[:, 1, 1] + rects_hei*0.5 

3699 nrects = len(rects) 

3700 prects = (rects_center_x, rects_center_y, num.arange(nrects), 

3701 num.zeros(nrects), rects_hei, rects_wid) 

3702 

3703 # points = num.array(corners, dtype=float) 

3704 

3705 cptfile = self.tempfilename() + '.cpt' 

3706 self.makecpt( 

3707 C='ocean', 

3708 T='%g/%g/%g' % (-nrects, nrects, 1), 

3709 Z=True, 

3710 out_filename=cptfile, suppress_defaults=True) 

3711 

3712 bb = layout.bbox() 

3713 self.psxy( 

3714 in_columns=prects, 

3715 C=cptfile, 

3716 W='1p', 

3717 S='J', 

3718 R=(bb[0], bb[2], bb[1], bb[3]), 

3719 *layout.XYJ()) 

3720 

3721 

3722def simpleconf_to_ax(conf, axname): 

3723 c = {} 

3724 x = axname 

3725 for x in ('', axname): 

3726 for k in ('label', 'unit', 'scaled_unit', 'scaled_unit_factor', 

3727 'space', 'mode', 'approx_ticks', 'limits', 'masking', 'inc', 

3728 'snap'): 

3729 

3730 if x+k in conf: 

3731 c[k] = conf[x+k] 

3732 

3733 return Ax(**c) 

3734 

3735 

3736class DensityPlotDef(object): 

3737 def __init__(self, data, cpt='ocean', tension=0.7, size=(640, 480), 

3738 contour=False, method='surface', zscaler=None, **extra): 

3739 self.data = data 

3740 self.cpt = cpt 

3741 self.tension = tension 

3742 self.size = size 

3743 self.contour = contour 

3744 self.method = method 

3745 self.zscaler = zscaler 

3746 self.extra = extra 

3747 

3748 

3749class TextDef(object): 

3750 def __init__( 

3751 self, 

3752 data, 

3753 size=9, 

3754 justify='MC', 

3755 fontno=0, 

3756 offset=(0, 0), 

3757 color='black'): 

3758 

3759 self.data = data 

3760 self.size = size 

3761 self.justify = justify 

3762 self.fontno = fontno 

3763 self.offset = offset 

3764 self.color = color 

3765 

3766 

3767class Simple(object): 

3768 def __init__(self, gmtconfig=None, gmtversion='newest', **simple_config): 

3769 self.data = [] 

3770 self.symbols = [] 

3771 self.config = copy.deepcopy(simple_config) 

3772 self.gmtconfig = gmtconfig 

3773 self.density_plot_defs = [] 

3774 self.text_defs = [] 

3775 

3776 self.gmtversion = gmtversion 

3777 

3778 self.data_x = [] 

3779 self.symbols_x = [] 

3780 

3781 self.data_y = [] 

3782 self.symbols_y = [] 

3783 

3784 self.default_config = {} 

3785 self.set_defaults(width=15.*cm, 

3786 height=15.*cm / golden_ratio, 

3787 margins=(2.*cm, 2.*cm, 2.*cm, 2.*cm), 

3788 with_palette=False, 

3789 palette_offset=0.5*cm, 

3790 palette_width=None, 

3791 palette_height=None, 

3792 zlabeloffset=2*cm, 

3793 draw_layout=False) 

3794 

3795 self.setup_defaults() 

3796 self.fixate_widget_aspect = False 

3797 

3798 def setup_defaults(self): 

3799 pass 

3800 

3801 def set_defaults(self, **kwargs): 

3802 self.default_config.update(kwargs) 

3803 

3804 def plot(self, data, symbol=''): 

3805 self.data.append(data) 

3806 self.symbols.append(symbol) 

3807 

3808 def density_plot(self, data, **kwargs): 

3809 dpd = DensityPlotDef(data, **kwargs) 

3810 self.density_plot_defs.append(dpd) 

3811 

3812 def text(self, data, **kwargs): 

3813 dpd = TextDef(data, **kwargs) 

3814 self.text_defs.append(dpd) 

3815 

3816 def plot_x(self, data, symbol=''): 

3817 self.data_x.append(data) 

3818 self.symbols_x.append(symbol) 

3819 

3820 def plot_y(self, data, symbol=''): 

3821 self.data_y.append(data) 

3822 self.symbols_y.append(symbol) 

3823 

3824 def set(self, **kwargs): 

3825 self.config.update(kwargs) 

3826 

3827 def setup_base(self, conf): 

3828 w = conf.pop('width') 

3829 h = conf.pop('height') 

3830 margins = conf.pop('margins') 

3831 

3832 gmtconfig = {} 

3833 if self.gmtconfig is not None: 

3834 gmtconfig.update(self.gmtconfig) 

3835 

3836 gmt = GMT( 

3837 version=self.gmtversion, 

3838 config=gmtconfig, 

3839 config_papersize='Custom_%ix%i' % (w, h)) 

3840 

3841 layout = gmt.default_layout(with_palette=conf['with_palette']) 

3842 layout.set_min_margins(*margins) 

3843 if conf['with_palette']: 

3844 widget = layout.get_widget().get_widget(0, 0) 

3845 spacer = layout.get_widget().get_widget(1, 0) 

3846 spacer.set_horizontal(conf['palette_offset']) 

3847 palette_widget = layout.get_widget().get_widget(2, 0) 

3848 if conf['palette_width'] is not None: 

3849 palette_widget.set_horizontal(conf['palette_width']) 

3850 if conf['palette_height'] is not None: 

3851 palette_widget.set_vertical(conf['palette_height']) 

3852 widget.set_vertical(h-margins[2]-margins[3]-0.03*cm) 

3853 return gmt, layout, widget, palette_widget 

3854 else: 

3855 widget = layout.get_widget() 

3856 return gmt, layout, widget, None 

3857 

3858 def setup_projection(self, widget, scaler, conf): 

3859 pass 

3860 

3861 def setup_scaling(self, conf): 

3862 ndims = 2 

3863 if self.density_plot_defs: 

3864 ndims = 3 

3865 

3866 axes = [simpleconf_to_ax(conf, x) for x in 'xyz'[:ndims]] 

3867 

3868 data_all = [] 

3869 data_all.extend(self.data) 

3870 for dsd in self.density_plot_defs: 

3871 if dsd.zscaler is None: 

3872 data_all.append(dsd.data) 

3873 else: 

3874 data_all.append(dsd.data[:2]) 

3875 data_chopped = [ds[:ndims] for ds in data_all] 

3876 

3877 scaler = ScaleGuru(data_chopped, axes=axes[:ndims]) 

3878 

3879 self.setup_scaling_plus(scaler, axes[:ndims]) 

3880 

3881 return scaler 

3882 

3883 def setup_scaling_plus(self, scaler, axes): 

3884 pass 

3885 

3886 def setup_scaling_extra(self, scaler, conf): 

3887 

3888 scaler_x = scaler.copy() 

3889 scaler_x.data_ranges[1] = (0., 1.) 

3890 scaler_x.axes[1].mode = 'off' 

3891 

3892 scaler_y = scaler.copy() 

3893 scaler_y.data_ranges[0] = (0., 1.) 

3894 scaler_y.axes[0].mode = 'off' 

3895 

3896 return scaler_x, scaler_y 

3897 

3898 def draw_density(self, gmt, widget, scaler): 

3899 

3900 R = scaler.R() 

3901 # par = scaler.get_params() 

3902 rxyj = R + widget.XYJ() 

3903 innerticks = False 

3904 for dpd in self.density_plot_defs: 

3905 

3906 fn_cpt = gmt.tempfilename() + '.cpt' 

3907 

3908 if dpd.zscaler is not None: 

3909 s = dpd.zscaler 

3910 else: 

3911 s = scaler 

3912 

3913 gmt.makecpt(C=dpd.cpt, out_filename=fn_cpt, *s.T()) 

3914 

3915 fn_grid = gmt.tempfilename() 

3916 

3917 fn_mean = gmt.tempfilename() 

3918 

3919 if dpd.method in ('surface', 'triangulate'): 

3920 gmt.blockmean(in_columns=dpd.data, 

3921 I='%i+/%i+' % dpd.size, # noqa 

3922 out_filename=fn_mean, *R) 

3923 

3924 if dpd.method == 'surface': 

3925 gmt.surface( 

3926 in_filename=fn_mean, 

3927 T=dpd.tension, 

3928 G=fn_grid, 

3929 I='%i+/%i+' % dpd.size, # noqa 

3930 out_discard=True, 

3931 *R) 

3932 

3933 if dpd.method == 'triangulate': 

3934 gmt.triangulate( 

3935 in_filename=fn_mean, 

3936 G=fn_grid, 

3937 I='%i+/%i+' % dpd.size, # noqa 

3938 out_discard=True, 

3939 V=True, 

3940 *R) 

3941 

3942 if gmt.is_gmt5(): 

3943 gmt.grdimage(fn_grid, C=fn_cpt, E='i', n='l', *rxyj) 

3944 

3945 else: 

3946 gmt.grdimage(fn_grid, C=fn_cpt, E='i', S='l', *rxyj) 

3947 

3948 if dpd.contour: 

3949 gmt.grdcontour(fn_grid, C=fn_cpt, W='0.5p,black', *rxyj) 

3950 innerticks = '0.5p,black' 

3951 

3952 os.remove(fn_grid) 

3953 os.remove(fn_mean) 

3954 

3955 if dpd.method == 'fillcontour': 

3956 extra = dict(C=fn_cpt) 

3957 extra.update(dpd.extra) 

3958 gmt.pscontour(in_columns=dpd.data, 

3959 I=True, *rxyj, **extra) # noqa 

3960 

3961 if dpd.method == 'contour': 

3962 extra = dict(W='0.5p,black', C=fn_cpt) 

3963 extra.update(dpd.extra) 

3964 gmt.pscontour(in_columns=dpd.data, *rxyj, **extra) 

3965 

3966 return fn_cpt, innerticks 

3967 

3968 def draw_basemap(self, gmt, widget, scaler): 

3969 gmt.psbasemap(*(widget.JXY() + scaler.RB(ax_projection=True))) 

3970 

3971 def draw(self, gmt, widget, scaler): 

3972 rxyj = scaler.R() + widget.JXY() 

3973 for dat, sym in zip(self.data, self.symbols): 

3974 gmt.psxy(in_columns=dat, *(sym.split()+rxyj)) 

3975 

3976 def post_draw(self, gmt, widget, scaler): 

3977 pass 

3978 

3979 def pre_draw(self, gmt, widget, scaler): 

3980 pass 

3981 

3982 def draw_extra(self, gmt, widget, scaler_x, scaler_y): 

3983 

3984 for dat, sym in zip(self.data_x, self.symbols_x): 

3985 gmt.psxy(in_columns=dat, 

3986 *(sym.split() + scaler_x.R() + widget.JXY())) 

3987 

3988 for dat, sym in zip(self.data_y, self.symbols_y): 

3989 gmt.psxy(in_columns=dat, 

3990 *(sym.split() + scaler_y.R() + widget.JXY())) 

3991 

3992 def draw_text(self, gmt, widget, scaler): 

3993 

3994 rxyj = scaler.R() + widget.JXY() 

3995 for td in self.text_defs: 

3996 x, y = td.data[0:2] 

3997 text = td.data[-1] 

3998 size = td.size 

3999 angle = 0 

4000 fontno = td.fontno 

4001 justify = td.justify 

4002 color = td.color 

4003 if gmt.is_gmt5(): 

4004 gmt.pstext( 

4005 in_rows=[(x, y, text)], 

4006 F='+f%gp,%s,%s+a%g+j%s' % ( 

4007 size, fontno, color, angle, justify), 

4008 D='%gp/%gp' % td.offset, *rxyj) 

4009 else: 

4010 gmt.pstext( 

4011 in_rows=[(x, y, size, angle, fontno, justify, text)], 

4012 D='%gp/%gp' % td.offset, *rxyj) 

4013 

4014 def save(self, filename, resolution=150): 

4015 

4016 conf = dict(self.default_config) 

4017 conf.update(self.config) 

4018 

4019 gmt, layout, widget, palette_widget = self.setup_base(conf) 

4020 scaler = self.setup_scaling(conf) 

4021 scaler_x, scaler_y = self.setup_scaling_extra(scaler, conf) 

4022 

4023 self.setup_projection(widget, scaler, conf) 

4024 if self.fixate_widget_aspect: 

4025 aspect = aspect_for_projection( 

4026 gmt.installation['version'], *(widget.J() + scaler.R())) 

4027 

4028 widget.set_aspect(aspect) 

4029 

4030 if conf['draw_layout']: 

4031 gmt.draw_layout(layout) 

4032 cptfile = None 

4033 if self.density_plot_defs: 

4034 cptfile, innerticks = self.draw_density(gmt, widget, scaler) 

4035 self.pre_draw(gmt, widget, scaler) 

4036 self.draw(gmt, widget, scaler) 

4037 self.post_draw(gmt, widget, scaler) 

4038 self.draw_extra(gmt, widget, scaler_x, scaler_y) 

4039 self.draw_text(gmt, widget, scaler) 

4040 self.draw_basemap(gmt, widget, scaler) 

4041 

4042 if palette_widget and cptfile: 

4043 nice_palette(gmt, palette_widget, scaler, cptfile, 

4044 innerticks=innerticks, 

4045 zlabeloffset=conf['zlabeloffset']) 

4046 

4047 gmt.save(filename, resolution=resolution) 

4048 

4049 

4050class LinLinPlot(Simple): 

4051 pass 

4052 

4053 

4054class LogLinPlot(Simple): 

4055 

4056 def setup_defaults(self): 

4057 self.set_defaults(xmode='min-max') 

4058 

4059 def setup_projection(self, widget, scaler, conf): 

4060 widget['J'] = '-JX%(width)gpl/%(height)gp' 

4061 scaler['B'] = '-B2:%(xlabel)s:/%(yinc)g:%(ylabel)s:WSen' 

4062 

4063 

4064class LinLogPlot(Simple): 

4065 

4066 def setup_defaults(self): 

4067 self.set_defaults(ymode='min-max') 

4068 

4069 def setup_projection(self, widget, scaler, conf): 

4070 widget['J'] = '-JX%(width)gp/%(height)gpl' 

4071 scaler['B'] = '-B%(xinc)g:%(xlabel)s:/2:%(ylabel)s:WSen' 

4072 

4073 

4074class LogLogPlot(Simple): 

4075 

4076 def setup_defaults(self): 

4077 self.set_defaults(mode='min-max') 

4078 

4079 def setup_projection(self, widget, scaler, conf): 

4080 widget['J'] = '-JX%(width)gpl/%(height)gpl' 

4081 scaler['B'] = '-B2:%(xlabel)s:/2:%(ylabel)s:WSen' 

4082 

4083 

4084class AziDistPlot(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( 

4092 height=15.*cm, 

4093 width=15.*cm, 

4094 xmode='off', 

4095 xlimits=(0., 360.), 

4096 xinc=45.) 

4097 

4098 def setup_projection(self, widget, scaler, conf): 

4099 widget['J'] = '-JPa%(width)gp' 

4100 

4101 def setup_scaling_plus(self, scaler, axes): 

4102 scaler['B'] = '-B%(xinc)g:%(xlabel)s:/%(yinc)g:%(ylabel)s:N' 

4103 

4104 

4105class MPlot(Simple): 

4106 

4107 def __init__(self, *args, **kwargs): 

4108 Simple.__init__(self, *args, **kwargs) 

4109 self.fixate_widget_aspect = True 

4110 

4111 def setup_defaults(self): 

4112 self.set_defaults(xmode='min-max', ymode='min-max') 

4113 

4114 def setup_projection(self, widget, scaler, conf): 

4115 par = scaler.get_params() 

4116 lon0 = (par['xmin'] + par['xmax'])/2. 

4117 lat0 = (par['ymin'] + par['ymax'])/2. 

4118 sll = '%g/%g' % (lon0, lat0) 

4119 widget['J'] = '-JM' + sll + '/%(width)gp' 

4120 scaler['B'] = \ 

4121 '-B%(xinc)gg%(xinc)g:%(xlabel)s:/%(yinc)gg%(yinc)g:%(ylabel)s:WSen' 

4122 

4123 

4124def nice_palette(gmt, widget, scaleguru, cptfile, zlabeloffset=0.8*inch, 

4125 innerticks=True): 

4126 

4127 par = scaleguru.get_params() 

4128 par_ax = scaleguru.get_params(ax_projection=True) 

4129 nz_palette = int(widget.height()/inch * 300) 

4130 px = num.zeros(nz_palette*2) 

4131 px[1::2] += 1 

4132 pz = num.linspace(par['zmin'], par['zmax'], nz_palette).repeat(2) 

4133 pdz = pz[2]-pz[0] 

4134 palgrdfile = gmt.tempfilename() 

4135 pal_r = (0, 1, par['zmin'], par['zmax']) 

4136 pal_ax_r = (0, 1, par_ax['zmin'], par_ax['zmax']) 

4137 gmt.xyz2grd( 

4138 G=palgrdfile, R=pal_r, 

4139 I=(1, pdz), in_columns=(px, pz, pz), # noqa 

4140 out_discard=True) 

4141 

4142 gmt.grdimage(palgrdfile, R=pal_r, C=cptfile, *widget.JXY()) 

4143 if isinstance(innerticks, str): 

4144 tickpen = innerticks 

4145 gmt.grdcontour(palgrdfile, W=tickpen, R=pal_r, C=cptfile, 

4146 *widget.JXY()) 

4147 

4148 negpalwid = '%gp' % -widget.width() 

4149 if not isinstance(innerticks, str) and innerticks: 

4150 ticklen = negpalwid 

4151 else: 

4152 ticklen = '0p' 

4153 

4154 TICK_LENGTH_PARAM = 'MAP_TICK_LENGTH' if gmt.is_gmt5() else 'TICK_LENGTH' 

4155 gmt.psbasemap( 

4156 R=pal_ax_r, B='4::/%(zinc)g::nsw' % par_ax, 

4157 config={TICK_LENGTH_PARAM: ticklen}, 

4158 *widget.JXY()) 

4159 

4160 if innerticks: 

4161 gmt.psbasemap( 

4162 R=pal_ax_r, B='4::/%(zinc)g::E' % par_ax, 

4163 config={TICK_LENGTH_PARAM: '0p'}, 

4164 *widget.JXY()) 

4165 else: 

4166 gmt.psbasemap(R=pal_ax_r, B='4::/%(zinc)g::E' % par_ax, *widget.JXY()) 

4167 

4168 if par_ax['zlabel']: 

4169 label_font = gmt.label_font() 

4170 label_font_size = gmt.label_font_size() 

4171 label_offset = zlabeloffset 

4172 gmt.pstext( 

4173 R=(0, 1, 0, 2), D='%gp/0p' % label_offset, 

4174 N=True, 

4175 in_rows=[(1, 1, label_font_size, -90, label_font, 'CB', 

4176 par_ax['zlabel'])], 

4177 *widget.JXY())