# http://pyrocko.org - GPLv3 # # The Pyrocko Developers, 21st Century # ---|P------/S----------~Lg----------
# This file is part of GmtPy (http://emolch.github.io/gmtpy/) # See there for copying and licensing information.
'isolatin1+': 'iso-8859-1', 'standard+': 'ascii', 'isolatin1': 'iso-8859-1', 'standard': 'ascii'}
try: get_gmt_installation('newest') return True
except GMTInstallationProblem: return False
if not have_gmt(): raise ExternalProgramMissing('GMT is not installed or cannot be found')
for prog in [['pdftocairo'], ['convert'], ['gs', '-h']]: try: p = subprocess.Popen( prog, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdout, stderr) = p.communicate()
except OSError: return False
return True
width=None, height=None, size=None):
_, tmp_filename_base = tempfile.mkstemp()
try: if out_filename.endswith('.svg'): fmt_arg = '-svg' tmp_filename = tmp_filename_base oversample = 1.0 else: fmt_arg = '-png' tmp_filename = tmp_filename_base + '-1.png'
if size is not None: scale_args = ['-scale-to', '%i' % int(round(size*oversample))] elif width is not None: scale_args = ['-scale-to-x', '%i' % int(round(width*oversample))] elif height is not None: scale_args = ['-scale-to-y', '%i' % int(round(height*oversample))] else: scale_args = ['-r', '%i' % int(round(resolution * oversample))]
try: subprocess.check_call( ['pdftocairo'] + scale_args + [fmt_arg, in_filename, tmp_filename_base]) except OSError as e: raise GmtPyError( 'Cannot start `pdftocairo`, is it installed? (%s)' % str(e))
if oversample > 1.: try: subprocess.check_call([ 'convert', tmp_filename, '-resize', '%i%%' % int(round(100.0/oversample)), out_filename]) except OSError as e: raise GmtPyError( 'Cannot start `convert`, is it installed? (%s)' % str(e))
else: if out_filename.endswith('.png') or out_filename.endswith('.svg'): shutil.move(tmp_filename, out_filename) else: try: subprocess.check_call( ['convert', tmp_filename, out_filename]) except Exception as e: raise GmtPyError( 'Cannot start `convert`, is it installed? (%s)' % str(e))
except Exception: raise
finally: if os.path.exists(tmp_filename_base): os.remove(tmp_filename_base)
if os.path.exists(tmp_filename): os.remove(tmp_filename)
for pat in [find_hiresbb, find_bb]: m = pat.search(s) if m: bb = [float(x) for x in m.group(1).split()] return bb
raise GmtPyError('Cannot find bbox')
def repl(m): if m.group(1): return ('%%HiResBoundingBox: ' + ' '.join( '%.3f' % float(x) for x in bbox)).encode('ascii') else: return ('%%%%BoundingBox: %i %i %i %i' % ( int(math.floor(bbox[0])), int(math.floor(bbox[1])), int(math.ceil(bbox[2])), int(math.ceil(bbox[3])))).encode('ascii')
pat = re.compile(br'%%(HiRes)?BoundingBox:((\s+[0-9.]+){4})') if len(args) == 1: s = args[0] return pat.sub(repl, s)
else: fin, fout = args nn = 0 for line in fin: line, n = pat.subn(repl, line) nn += n fout.write(line) if nn == 2: break
if nn == 2: for line in fin: fout.write(line)
'''This function should be used for debugging output only - it could be insecure.'''
if re.search(r'[^a-zA-Z0-9._/=-]', s): return "'" + s.replace("'", "'\\''") + "'" else: return s
'''This function should be used for debugging output only - it could be insecure.'''
return ' '.join([escape_shell_arg(x) for x in args])
# units in points 'i': 72., 'c': 72./2.54, 'm': 72.*100./2.54, 'p': 1.}
# some awsome colors 'butter1': (252, 233, 79), 'butter2': (237, 212, 0), 'butter3': (196, 160, 0), 'chameleon1': (138, 226, 52), 'chameleon2': (115, 210, 22), 'chameleon3': (78, 154, 6), 'orange1': (252, 175, 62), 'orange2': (245, 121, 0), 'orange3': (206, 92, 0), 'skyblue1': (114, 159, 207), 'skyblue2': (52, 101, 164), 'skyblue3': (32, 74, 135), 'plum1': (173, 127, 168), 'plum2': (117, 80, 123), 'plum3': (92, 53, 102), 'chocolate1': (233, 185, 110), 'chocolate2': (193, 125, 17), 'chocolate3': (143, 89, 2), 'scarletred1': (239, 41, 41), 'scarletred2': (204, 0, 0), 'scarletred3': (164, 0, 0), 'aluminium1': (238, 238, 236), 'aluminium2': (211, 215, 207), 'aluminium3': (186, 189, 182), 'aluminium4': (136, 138, 133), 'aluminium5': (85, 87, 83), 'aluminium6': (46, 52, 54) }
'scarletred2', 'skyblue3', 'chameleon3', 'orange2', 'plum2', 'chocolate2', 'butter2')]
'''Generate a string for GMT option arguments expecting a color.
If ``x`` is None, a random color is returned. If it is an integer, the corresponding ``gmtpy.graph_colors[x]`` or black returned. If it is a string and the corresponding ``gmtpy.tango_colors[x]`` exists, this is returned, or the string is passed through. If ``x`` is a tuple, it is transformed into the string form which GMT expects. '''
if x is None: return '%i/%i/%i' % tuple(random.randint(0, 255) for _ in 'rgb')
if isinstance(x, int): if 0 <= x < len(graph_colors): return '%i/%i/%i' % graph_colors[x] else: return '0/0/0'
elif isinstance(x, str): if x in tango_colors: return '%i/%i/%i' % tango_colors[x] else: return x
return '%i/%i/%i' % x
if x is None: return tuple([random.randint(0, 255) for _x in 'rgb'])
if isinstance(x, int): if 0 <= x < len(graph_colors): return graph_colors[x] else: return (0, 0, 0)
elif isinstance(x, str): if x in tango_colors: return tango_colors[x]
return x
# Set fixed installation(s) to use... # (use this, if you want to use different GMT versions simultaneously.)
# _gmt_installations['4.2.1'] = {'home': '/sw/etch-ia32/gmt-4.2.1', # 'bin': '/sw/etch-ia32/gmt-4.2.1/bin'} # _gmt_installations['4.3.0'] = {'home': '/sw/etch-ia32/gmt-4.3.0', # 'bin': '/sw/etch-ia32/gmt-4.3.0/bin'} # _gmt_installations['4.3.1'] = {'home': '/sw/share/gmt', # 'bin': '/sw/bin' }
# ... or let GmtPy autodetect GMT via $PATH and $GMTHOME
a = a.split('_')[0] # get rid of revision id return [int(x) for x in a.split('.')]
return sorted(_gmt_installations.keys(), key=key_version)[-1]
return sorted(_gmt_installations.keys(), key=key_version)
# To have consistent defaults, they are hardcoded here and should not be # changed.
# # GMT-SYSTEM 4.2.1 Defaults file # #-------- Plot Media Parameters ------------- PAGE_COLOR = 255/255/255 PAGE_ORIENTATION = portrait PAPER_MEDIA = a4+ #-------- Basemap Annotation Parameters ------ ANNOT_MIN_ANGLE = 20 ANNOT_MIN_SPACING = 0 ANNOT_FONT_PRIMARY = Helvetica ANNOT_FONT_SIZE = 12p ANNOT_OFFSET_PRIMARY = 0.075i ANNOT_FONT_SECONDARY = Helvetica ANNOT_FONT_SIZE_SECONDARY = 16p ANNOT_OFFSET_SECONDARY = 0.075i DEGREE_SYMBOL = ring HEADER_FONT = Helvetica HEADER_FONT_SIZE = 36p HEADER_OFFSET = 0.1875i LABEL_FONT = Helvetica LABEL_FONT_SIZE = 14p LABEL_OFFSET = 0.1125i OBLIQUE_ANNOTATION = 1 PLOT_CLOCK_FORMAT = hh:mm:ss PLOT_DATE_FORMAT = yyyy-mm-dd PLOT_DEGREE_FORMAT = +ddd:mm:ss Y_AXIS_TYPE = hor_text #-------- Basemap Layout Parameters --------- BASEMAP_AXES = WESN BASEMAP_FRAME_RGB = 0/0/0 BASEMAP_TYPE = plain FRAME_PEN = 1.25p FRAME_WIDTH = 0.075i GRID_CROSS_SIZE_PRIMARY = 0i GRID_CROSS_SIZE_SECONDARY = 0i GRID_PEN_PRIMARY = 0.25p GRID_PEN_SECONDARY = 0.5p MAP_SCALE_HEIGHT = 0.075i TICK_LENGTH = 0.075i POLAR_CAP = 85/90 TICK_PEN = 0.5p X_AXIS_LENGTH = 9i Y_AXIS_LENGTH = 6i X_ORIGIN = 1i Y_ORIGIN = 1i UNIX_TIME = FALSE UNIX_TIME_POS = -0.75i/-0.75i #-------- Color System Parameters ----------- COLOR_BACKGROUND = 0/0/0 COLOR_FOREGROUND = 255/255/255 COLOR_NAN = 128/128/128 COLOR_IMAGE = adobe COLOR_MODEL = rgb HSV_MIN_SATURATION = 1 HSV_MAX_SATURATION = 0.1 HSV_MIN_VALUE = 0.3 HSV_MAX_VALUE = 1 #-------- PostScript Parameters ------------- CHAR_ENCODING = ISOLatin1+ DOTS_PR_INCH = 300 N_COPIES = 1 PS_COLOR = rgb PS_IMAGE_COMPRESS = none PS_IMAGE_FORMAT = ascii PS_LINE_CAP = round PS_LINE_JOIN = miter PS_MITER_LIMIT = 35 PS_VERBOSE = FALSE GLOBAL_X_SCALE = 1 GLOBAL_Y_SCALE = 1 #-------- I/O Format Parameters ------------- D_FORMAT = %lg FIELD_DELIMITER = tab GRIDFILE_SHORTHAND = FALSE GRID_FORMAT = nf INPUT_CLOCK_FORMAT = hh:mm:ss INPUT_DATE_FORMAT = yyyy-mm-dd IO_HEADER = FALSE N_HEADER_RECS = 1 OUTPUT_CLOCK_FORMAT = hh:mm:ss OUTPUT_DATE_FORMAT = yyyy-mm-dd OUTPUT_DEGREE_FORMAT = +D XY_TOGGLE = FALSE #-------- Projection Parameters ------------- ELLIPSOID = WGS-84 MAP_SCALE_FACTOR = default MEASURE_UNIT = inch #-------- Calendar/Time Parameters ---------- TIME_FORMAT_PRIMARY = full TIME_FORMAT_SECONDARY = full TIME_EPOCH = 2000-01-01T00:00:00 TIME_IS_INTERVAL = OFF TIME_INTERVAL_FRACTION = 0.5 TIME_LANGUAGE = us TIME_SYSTEM = other TIME_UNIT = d TIME_WEEK_START = Sunday Y2K_OFFSET_YEAR = 1950 #-------- Miscellaneous Parameters ---------- HISTORY = TRUE INTERPOLANT = akima LINE_STEP = 0.01i VECTOR_SHAPE = 0 VERBOSE = FALSE'''
# # GMT-SYSTEM 4.3.0 Defaults file # #-------- Plot Media Parameters ------------- PAGE_COLOR = 255/255/255 PAGE_ORIENTATION = portrait PAPER_MEDIA = a4+ #-------- Basemap Annotation Parameters ------ ANNOT_MIN_ANGLE = 20 ANNOT_MIN_SPACING = 0 ANNOT_FONT_PRIMARY = Helvetica ANNOT_FONT_SIZE_PRIMARY = 12p ANNOT_OFFSET_PRIMARY = 0.075i ANNOT_FONT_SECONDARY = Helvetica ANNOT_FONT_SIZE_SECONDARY = 16p ANNOT_OFFSET_SECONDARY = 0.075i DEGREE_SYMBOL = ring HEADER_FONT = Helvetica HEADER_FONT_SIZE = 36p HEADER_OFFSET = 0.1875i LABEL_FONT = Helvetica LABEL_FONT_SIZE = 14p LABEL_OFFSET = 0.1125i OBLIQUE_ANNOTATION = 1 PLOT_CLOCK_FORMAT = hh:mm:ss PLOT_DATE_FORMAT = yyyy-mm-dd PLOT_DEGREE_FORMAT = +ddd:mm:ss Y_AXIS_TYPE = hor_text #-------- Basemap Layout Parameters --------- BASEMAP_AXES = WESN BASEMAP_FRAME_RGB = 0/0/0 BASEMAP_TYPE = plain FRAME_PEN = 1.25p FRAME_WIDTH = 0.075i GRID_CROSS_SIZE_PRIMARY = 0i GRID_PEN_PRIMARY = 0.25p GRID_CROSS_SIZE_SECONDARY = 0i GRID_PEN_SECONDARY = 0.5p MAP_SCALE_HEIGHT = 0.075i POLAR_CAP = 85/90 TICK_LENGTH = 0.075i TICK_PEN = 0.5p X_AXIS_LENGTH = 9i Y_AXIS_LENGTH = 6i X_ORIGIN = 1i Y_ORIGIN = 1i UNIX_TIME = FALSE UNIX_TIME_POS = BL/-0.75i/-0.75i UNIX_TIME_FORMAT = %Y %b %d %H:%M:%S #-------- Color System Parameters ----------- COLOR_BACKGROUND = 0/0/0 COLOR_FOREGROUND = 255/255/255 COLOR_NAN = 128/128/128 COLOR_IMAGE = adobe COLOR_MODEL = rgb HSV_MIN_SATURATION = 1 HSV_MAX_SATURATION = 0.1 HSV_MIN_VALUE = 0.3 HSV_MAX_VALUE = 1 #-------- PostScript Parameters ------------- CHAR_ENCODING = ISOLatin1+ DOTS_PR_INCH = 300 N_COPIES = 1 PS_COLOR = rgb PS_IMAGE_COMPRESS = none PS_IMAGE_FORMAT = ascii PS_LINE_CAP = round PS_LINE_JOIN = miter PS_MITER_LIMIT = 35 PS_VERBOSE = FALSE GLOBAL_X_SCALE = 1 GLOBAL_Y_SCALE = 1 #-------- I/O Format Parameters ------------- D_FORMAT = %lg FIELD_DELIMITER = tab GRIDFILE_SHORTHAND = FALSE GRID_FORMAT = nf INPUT_CLOCK_FORMAT = hh:mm:ss INPUT_DATE_FORMAT = yyyy-mm-dd IO_HEADER = FALSE N_HEADER_RECS = 1 OUTPUT_CLOCK_FORMAT = hh:mm:ss OUTPUT_DATE_FORMAT = yyyy-mm-dd OUTPUT_DEGREE_FORMAT = +D XY_TOGGLE = FALSE #-------- Projection Parameters ------------- ELLIPSOID = WGS-84 MAP_SCALE_FACTOR = default MEASURE_UNIT = inch #-------- Calendar/Time Parameters ---------- TIME_FORMAT_PRIMARY = full TIME_FORMAT_SECONDARY = full TIME_EPOCH = 2000-01-01T00:00:00 TIME_IS_INTERVAL = OFF TIME_INTERVAL_FRACTION = 0.5 TIME_LANGUAGE = us TIME_UNIT = d TIME_WEEK_START = Sunday Y2K_OFFSET_YEAR = 1950 #-------- Miscellaneous Parameters ---------- HISTORY = TRUE INTERPOLANT = akima LINE_STEP = 0.01i VECTOR_SHAPE = 0 VERBOSE = FALSE'''
# # GMT-SYSTEM 4.3.1 Defaults file # #-------- Plot Media Parameters ------------- PAGE_COLOR = 255/255/255 PAGE_ORIENTATION = portrait PAPER_MEDIA = a4+ #-------- Basemap Annotation Parameters ------ ANNOT_MIN_ANGLE = 20 ANNOT_MIN_SPACING = 0 ANNOT_FONT_PRIMARY = Helvetica ANNOT_FONT_SIZE_PRIMARY = 12p ANNOT_OFFSET_PRIMARY = 0.075i ANNOT_FONT_SECONDARY = Helvetica ANNOT_FONT_SIZE_SECONDARY = 16p ANNOT_OFFSET_SECONDARY = 0.075i DEGREE_SYMBOL = ring HEADER_FONT = Helvetica HEADER_FONT_SIZE = 36p HEADER_OFFSET = 0.1875i LABEL_FONT = Helvetica LABEL_FONT_SIZE = 14p LABEL_OFFSET = 0.1125i OBLIQUE_ANNOTATION = 1 PLOT_CLOCK_FORMAT = hh:mm:ss PLOT_DATE_FORMAT = yyyy-mm-dd PLOT_DEGREE_FORMAT = +ddd:mm:ss Y_AXIS_TYPE = hor_text #-------- Basemap Layout Parameters --------- BASEMAP_AXES = WESN BASEMAP_FRAME_RGB = 0/0/0 BASEMAP_TYPE = plain FRAME_PEN = 1.25p FRAME_WIDTH = 0.075i GRID_CROSS_SIZE_PRIMARY = 0i GRID_PEN_PRIMARY = 0.25p GRID_CROSS_SIZE_SECONDARY = 0i GRID_PEN_SECONDARY = 0.5p MAP_SCALE_HEIGHT = 0.075i POLAR_CAP = 85/90 TICK_LENGTH = 0.075i TICK_PEN = 0.5p X_AXIS_LENGTH = 9i Y_AXIS_LENGTH = 6i X_ORIGIN = 1i Y_ORIGIN = 1i UNIX_TIME = FALSE UNIX_TIME_POS = BL/-0.75i/-0.75i UNIX_TIME_FORMAT = %Y %b %d %H:%M:%S #-------- Color System Parameters ----------- COLOR_BACKGROUND = 0/0/0 COLOR_FOREGROUND = 255/255/255 COLOR_NAN = 128/128/128 COLOR_IMAGE = adobe COLOR_MODEL = rgb HSV_MIN_SATURATION = 1 HSV_MAX_SATURATION = 0.1 HSV_MIN_VALUE = 0.3 HSV_MAX_VALUE = 1 #-------- PostScript Parameters ------------- CHAR_ENCODING = ISOLatin1+ DOTS_PR_INCH = 300 N_COPIES = 1 PS_COLOR = rgb PS_IMAGE_COMPRESS = none PS_IMAGE_FORMAT = ascii PS_LINE_CAP = round PS_LINE_JOIN = miter PS_MITER_LIMIT = 35 PS_VERBOSE = FALSE GLOBAL_X_SCALE = 1 GLOBAL_Y_SCALE = 1 #-------- I/O Format Parameters ------------- D_FORMAT = %lg FIELD_DELIMITER = tab GRIDFILE_SHORTHAND = FALSE GRID_FORMAT = nf INPUT_CLOCK_FORMAT = hh:mm:ss INPUT_DATE_FORMAT = yyyy-mm-dd IO_HEADER = FALSE N_HEADER_RECS = 1 OUTPUT_CLOCK_FORMAT = hh:mm:ss OUTPUT_DATE_FORMAT = yyyy-mm-dd OUTPUT_DEGREE_FORMAT = +D XY_TOGGLE = FALSE #-------- Projection Parameters ------------- ELLIPSOID = WGS-84 MAP_SCALE_FACTOR = default MEASURE_UNIT = inch #-------- Calendar/Time Parameters ---------- TIME_FORMAT_PRIMARY = full TIME_FORMAT_SECONDARY = full TIME_EPOCH = 2000-01-01T00:00:00 TIME_IS_INTERVAL = OFF TIME_INTERVAL_FRACTION = 0.5 TIME_LANGUAGE = us TIME_UNIT = d TIME_WEEK_START = Sunday Y2K_OFFSET_YEAR = 1950 #-------- Miscellaneous Parameters ---------- HISTORY = TRUE INTERPOLANT = akima LINE_STEP = 0.01i VECTOR_SHAPE = 0 VERBOSE = FALSE'''
# # GMT-SYSTEM 4.4.0 [64-bit] Defaults file # #-------- Plot Media Parameters ------------- PAGE_COLOR = 255/255/255 PAGE_ORIENTATION = portrait PAPER_MEDIA = a4+ #-------- Basemap Annotation Parameters ------ ANNOT_MIN_ANGLE = 20 ANNOT_MIN_SPACING = 0 ANNOT_FONT_PRIMARY = Helvetica ANNOT_FONT_SIZE_PRIMARY = 14p ANNOT_OFFSET_PRIMARY = 0.075i ANNOT_FONT_SECONDARY = Helvetica ANNOT_FONT_SIZE_SECONDARY = 16p ANNOT_OFFSET_SECONDARY = 0.075i DEGREE_SYMBOL = ring HEADER_FONT = Helvetica HEADER_FONT_SIZE = 36p HEADER_OFFSET = 0.1875i LABEL_FONT = Helvetica LABEL_FONT_SIZE = 14p LABEL_OFFSET = 0.1125i OBLIQUE_ANNOTATION = 1 PLOT_CLOCK_FORMAT = hh:mm:ss PLOT_DATE_FORMAT = yyyy-mm-dd PLOT_DEGREE_FORMAT = +ddd:mm:ss Y_AXIS_TYPE = hor_text #-------- Basemap Layout Parameters --------- BASEMAP_AXES = WESN BASEMAP_FRAME_RGB = 0/0/0 BASEMAP_TYPE = plain FRAME_PEN = 1.25p FRAME_WIDTH = 0.075i GRID_CROSS_SIZE_PRIMARY = 0i GRID_PEN_PRIMARY = 0.25p GRID_CROSS_SIZE_SECONDARY = 0i GRID_PEN_SECONDARY = 0.5p MAP_SCALE_HEIGHT = 0.075i POLAR_CAP = 85/90 TICK_LENGTH = 0.075i TICK_PEN = 0.5p X_AXIS_LENGTH = 9i Y_AXIS_LENGTH = 6i X_ORIGIN = 1i Y_ORIGIN = 1i UNIX_TIME = FALSE UNIX_TIME_POS = BL/-0.75i/-0.75i UNIX_TIME_FORMAT = %Y %b %d %H:%M:%S #-------- Color System Parameters ----------- COLOR_BACKGROUND = 0/0/0 COLOR_FOREGROUND = 255/255/255 COLOR_NAN = 128/128/128 COLOR_IMAGE = adobe COLOR_MODEL = rgb HSV_MIN_SATURATION = 1 HSV_MAX_SATURATION = 0.1 HSV_MIN_VALUE = 0.3 HSV_MAX_VALUE = 1 #-------- PostScript Parameters ------------- CHAR_ENCODING = ISOLatin1+ DOTS_PR_INCH = 300 N_COPIES = 1 PS_COLOR = rgb PS_IMAGE_COMPRESS = lzw PS_IMAGE_FORMAT = ascii PS_LINE_CAP = round PS_LINE_JOIN = miter PS_MITER_LIMIT = 35 PS_VERBOSE = FALSE GLOBAL_X_SCALE = 1 GLOBAL_Y_SCALE = 1 #-------- I/O Format Parameters ------------- D_FORMAT = %lg FIELD_DELIMITER = tab GRIDFILE_SHORTHAND = FALSE GRID_FORMAT = nf INPUT_CLOCK_FORMAT = hh:mm:ss INPUT_DATE_FORMAT = yyyy-mm-dd IO_HEADER = FALSE N_HEADER_RECS = 1 OUTPUT_CLOCK_FORMAT = hh:mm:ss OUTPUT_DATE_FORMAT = yyyy-mm-dd OUTPUT_DEGREE_FORMAT = +D XY_TOGGLE = FALSE #-------- Projection Parameters ------------- ELLIPSOID = WGS-84 MAP_SCALE_FACTOR = default MEASURE_UNIT = inch #-------- Calendar/Time Parameters ---------- TIME_FORMAT_PRIMARY = full TIME_FORMAT_SECONDARY = full TIME_EPOCH = 2000-01-01T00:00:00 TIME_IS_INTERVAL = OFF TIME_INTERVAL_FRACTION = 0.5 TIME_LANGUAGE = us TIME_UNIT = d TIME_WEEK_START = Sunday Y2K_OFFSET_YEAR = 1950 #-------- Miscellaneous Parameters ---------- HISTORY = TRUE INTERPOLANT = akima LINE_STEP = 0.01i VECTOR_SHAPE = 0 VERBOSE = FALSE '''
# # GMT-SYSTEM 4.5.2 [64-bit] Defaults file # #-------- Plot Media Parameters ------------- PAGE_COLOR = white PAGE_ORIENTATION = portrait PAPER_MEDIA = a4+ #-------- Basemap Annotation Parameters ------ ANNOT_MIN_ANGLE = 20 ANNOT_MIN_SPACING = 0 ANNOT_FONT_PRIMARY = Helvetica ANNOT_FONT_SIZE_PRIMARY = 14p ANNOT_OFFSET_PRIMARY = 0.075i ANNOT_FONT_SECONDARY = Helvetica ANNOT_FONT_SIZE_SECONDARY = 16p ANNOT_OFFSET_SECONDARY = 0.075i DEGREE_SYMBOL = ring HEADER_FONT = Helvetica HEADER_FONT_SIZE = 36p HEADER_OFFSET = 0.1875i LABEL_FONT = Helvetica LABEL_FONT_SIZE = 14p LABEL_OFFSET = 0.1125i OBLIQUE_ANNOTATION = 1 PLOT_CLOCK_FORMAT = hh:mm:ss PLOT_DATE_FORMAT = yyyy-mm-dd PLOT_DEGREE_FORMAT = +ddd:mm:ss Y_AXIS_TYPE = hor_text #-------- Basemap Layout Parameters --------- BASEMAP_AXES = WESN BASEMAP_FRAME_RGB = black BASEMAP_TYPE = plain FRAME_PEN = 1.25p FRAME_WIDTH = 0.075i GRID_CROSS_SIZE_PRIMARY = 0i GRID_PEN_PRIMARY = 0.25p GRID_CROSS_SIZE_SECONDARY = 0i GRID_PEN_SECONDARY = 0.5p MAP_SCALE_HEIGHT = 0.075i POLAR_CAP = 85/90 TICK_LENGTH = 0.075i TICK_PEN = 0.5p X_AXIS_LENGTH = 9i Y_AXIS_LENGTH = 6i X_ORIGIN = 1i Y_ORIGIN = 1i UNIX_TIME = FALSE UNIX_TIME_POS = BL/-0.75i/-0.75i UNIX_TIME_FORMAT = %Y %b %d %H:%M:%S #-------- Color System Parameters ----------- COLOR_BACKGROUND = black COLOR_FOREGROUND = white COLOR_NAN = 128 COLOR_IMAGE = adobe COLOR_MODEL = rgb HSV_MIN_SATURATION = 1 HSV_MAX_SATURATION = 0.1 HSV_MIN_VALUE = 0.3 HSV_MAX_VALUE = 1 #-------- PostScript Parameters ------------- CHAR_ENCODING = ISOLatin1+ DOTS_PR_INCH = 300 GLOBAL_X_SCALE = 1 GLOBAL_Y_SCALE = 1 N_COPIES = 1 PS_COLOR = rgb PS_IMAGE_COMPRESS = lzw PS_IMAGE_FORMAT = ascii PS_LINE_CAP = round PS_LINE_JOIN = miter PS_MITER_LIMIT = 35 PS_VERBOSE = FALSE TRANSPARENCY = 0 #-------- I/O Format Parameters ------------- D_FORMAT = %.12lg FIELD_DELIMITER = tab GRIDFILE_FORMAT = nf GRIDFILE_SHORTHAND = FALSE INPUT_CLOCK_FORMAT = hh:mm:ss INPUT_DATE_FORMAT = yyyy-mm-dd IO_HEADER = FALSE N_HEADER_RECS = 1 NAN_RECORDS = pass OUTPUT_CLOCK_FORMAT = hh:mm:ss OUTPUT_DATE_FORMAT = yyyy-mm-dd OUTPUT_DEGREE_FORMAT = D XY_TOGGLE = FALSE #-------- Projection Parameters ------------- ELLIPSOID = WGS-84 MAP_SCALE_FACTOR = default MEASURE_UNIT = inch #-------- Calendar/Time Parameters ---------- TIME_FORMAT_PRIMARY = full TIME_FORMAT_SECONDARY = full TIME_EPOCH = 2000-01-01T00:00:00 TIME_IS_INTERVAL = OFF TIME_INTERVAL_FRACTION = 0.5 TIME_LANGUAGE = us TIME_UNIT = d TIME_WEEK_START = Sunday Y2K_OFFSET_YEAR = 1950 #-------- Miscellaneous Parameters ---------- HISTORY = TRUE INTERPOLANT = akima LINE_STEP = 0.01i VECTOR_SHAPE = 0 VERBOSE = FALSE '''
# # GMT-SYSTEM 4.5.3 (CVS Jun 18 2010 10:56:07) [64-bit] Defaults file # #-------- Plot Media Parameters ------------- PAGE_COLOR = white PAGE_ORIENTATION = portrait PAPER_MEDIA = a4+ #-------- Basemap Annotation Parameters ------ ANNOT_MIN_ANGLE = 20 ANNOT_MIN_SPACING = 0 ANNOT_FONT_PRIMARY = Helvetica ANNOT_FONT_SIZE_PRIMARY = 14p ANNOT_OFFSET_PRIMARY = 0.075i ANNOT_FONT_SECONDARY = Helvetica ANNOT_FONT_SIZE_SECONDARY = 16p ANNOT_OFFSET_SECONDARY = 0.075i DEGREE_SYMBOL = ring HEADER_FONT = Helvetica HEADER_FONT_SIZE = 36p HEADER_OFFSET = 0.1875i LABEL_FONT = Helvetica LABEL_FONT_SIZE = 14p LABEL_OFFSET = 0.1125i OBLIQUE_ANNOTATION = 1 PLOT_CLOCK_FORMAT = hh:mm:ss PLOT_DATE_FORMAT = yyyy-mm-dd PLOT_DEGREE_FORMAT = +ddd:mm:ss Y_AXIS_TYPE = hor_text #-------- Basemap Layout Parameters --------- BASEMAP_AXES = WESN BASEMAP_FRAME_RGB = black BASEMAP_TYPE = plain FRAME_PEN = 1.25p FRAME_WIDTH = 0.075i GRID_CROSS_SIZE_PRIMARY = 0i GRID_PEN_PRIMARY = 0.25p GRID_CROSS_SIZE_SECONDARY = 0i GRID_PEN_SECONDARY = 0.5p MAP_SCALE_HEIGHT = 0.075i POLAR_CAP = 85/90 TICK_LENGTH = 0.075i TICK_PEN = 0.5p X_AXIS_LENGTH = 9i Y_AXIS_LENGTH = 6i X_ORIGIN = 1i Y_ORIGIN = 1i UNIX_TIME = FALSE UNIX_TIME_POS = BL/-0.75i/-0.75i UNIX_TIME_FORMAT = %Y %b %d %H:%M:%S #-------- Color System Parameters ----------- COLOR_BACKGROUND = black COLOR_FOREGROUND = white COLOR_NAN = 128 COLOR_IMAGE = adobe COLOR_MODEL = rgb HSV_MIN_SATURATION = 1 HSV_MAX_SATURATION = 0.1 HSV_MIN_VALUE = 0.3 HSV_MAX_VALUE = 1 #-------- PostScript Parameters ------------- CHAR_ENCODING = ISOLatin1+ DOTS_PR_INCH = 300 GLOBAL_X_SCALE = 1 GLOBAL_Y_SCALE = 1 N_COPIES = 1 PS_COLOR = rgb PS_IMAGE_COMPRESS = lzw PS_IMAGE_FORMAT = ascii PS_LINE_CAP = round PS_LINE_JOIN = miter PS_MITER_LIMIT = 35 PS_VERBOSE = FALSE TRANSPARENCY = 0 #-------- I/O Format Parameters ------------- D_FORMAT = %.12lg FIELD_DELIMITER = tab GRIDFILE_FORMAT = nf GRIDFILE_SHORTHAND = FALSE INPUT_CLOCK_FORMAT = hh:mm:ss INPUT_DATE_FORMAT = yyyy-mm-dd IO_HEADER = FALSE N_HEADER_RECS = 1 NAN_RECORDS = pass OUTPUT_CLOCK_FORMAT = hh:mm:ss OUTPUT_DATE_FORMAT = yyyy-mm-dd OUTPUT_DEGREE_FORMAT = D XY_TOGGLE = FALSE #-------- Projection Parameters ------------- ELLIPSOID = WGS-84 MAP_SCALE_FACTOR = default MEASURE_UNIT = inch #-------- Calendar/Time Parameters ---------- TIME_FORMAT_PRIMARY = full TIME_FORMAT_SECONDARY = full TIME_EPOCH = 2000-01-01T00:00:00 TIME_IS_INTERVAL = OFF TIME_INTERVAL_FRACTION = 0.5 TIME_LANGUAGE = us TIME_UNIT = d TIME_WEEK_START = Sunday Y2K_OFFSET_YEAR = 1950 #-------- Miscellaneous Parameters ---------- HISTORY = TRUE INTERPOLANT = akima LINE_STEP = 0.01i VECTOR_SHAPE = 0 VERBOSE = FALSE '''
# # GMT 5.1.2 Defaults file # vim:sw=8:ts=8:sts=8 # $Revision: 13836 $ # $LastChangedDate: 2014-12-20 03:45:42 -1000 (Sat, 20 Dec 2014) $ # # COLOR Parameters # COLOR_BACKGROUND = black COLOR_FOREGROUND = white COLOR_NAN = 127.5 COLOR_MODEL = none COLOR_HSV_MIN_S = 1 COLOR_HSV_MAX_S = 0.1 COLOR_HSV_MIN_V = 0.3 COLOR_HSV_MAX_V = 1 # # DIR Parameters # DIR_DATA = DIR_DCW = DIR_GSHHG = # # FONT Parameters # FONT_ANNOT_PRIMARY = 14p,Helvetica,black FONT_ANNOT_SECONDARY = 16p,Helvetica,black FONT_LABEL = 14p,Helvetica,black FONT_LOGO = 8p,Helvetica,black FONT_TITLE = 24p,Helvetica,black # # FORMAT Parameters # FORMAT_CLOCK_IN = hh:mm:ss FORMAT_CLOCK_OUT = hh:mm:ss FORMAT_CLOCK_MAP = hh:mm:ss FORMAT_DATE_IN = yyyy-mm-dd FORMAT_DATE_OUT = yyyy-mm-dd FORMAT_DATE_MAP = yyyy-mm-dd FORMAT_GEO_OUT = D FORMAT_GEO_MAP = ddd:mm:ss FORMAT_FLOAT_OUT = %.12g FORMAT_FLOAT_MAP = %.12g FORMAT_TIME_PRIMARY_MAP = full FORMAT_TIME_SECONDARY_MAP = full FORMAT_TIME_STAMP = %Y %b %d %H:%M:%S # # GMT Miscellaneous Parameters # GMT_COMPATIBILITY = 4 GMT_CUSTOM_LIBS = GMT_EXTRAPOLATE_VAL = NaN GMT_FFT = auto GMT_HISTORY = true GMT_INTERPOLANT = akima GMT_TRIANGULATE = Shewchuk GMT_VERBOSE = compat GMT_LANGUAGE = us # # I/O Parameters # IO_COL_SEPARATOR = tab IO_GRIDFILE_FORMAT = nf IO_GRIDFILE_SHORTHAND = false IO_HEADER = false IO_N_HEADER_RECS = 0 IO_NAN_RECORDS = pass IO_NC4_CHUNK_SIZE = auto IO_NC4_DEFLATION_LEVEL = 3 IO_LONLAT_TOGGLE = false IO_SEGMENT_MARKER = > # # MAP Parameters # MAP_ANNOT_MIN_ANGLE = 20 MAP_ANNOT_MIN_SPACING = 0p MAP_ANNOT_OBLIQUE = 1 MAP_ANNOT_OFFSET_PRIMARY = 0.075i MAP_ANNOT_OFFSET_SECONDARY = 0.075i MAP_ANNOT_ORTHO = we MAP_DEFAULT_PEN = default,black MAP_DEGREE_SYMBOL = ring MAP_FRAME_AXES = WESNZ MAP_FRAME_PEN = thicker,black MAP_FRAME_TYPE = fancy MAP_FRAME_WIDTH = 5p MAP_GRID_CROSS_SIZE_PRIMARY = 0p MAP_GRID_CROSS_SIZE_SECONDARY = 0p MAP_GRID_PEN_PRIMARY = default,black MAP_GRID_PEN_SECONDARY = thinner,black MAP_LABEL_OFFSET = 0.1944i MAP_LINE_STEP = 0.75p MAP_LOGO = false MAP_LOGO_POS = BL/-54p/-54p MAP_ORIGIN_X = 1i MAP_ORIGIN_Y = 1i MAP_POLAR_CAP = 85/90 MAP_SCALE_HEIGHT = 5p MAP_TICK_LENGTH_PRIMARY = 5p/2.5p MAP_TICK_LENGTH_SECONDARY = 15p/3.75p MAP_TICK_PEN_PRIMARY = thinner,black MAP_TICK_PEN_SECONDARY = thinner,black MAP_TITLE_OFFSET = 14p MAP_VECTOR_SHAPE = 0 # # Projection Parameters # PROJ_AUX_LATITUDE = authalic PROJ_ELLIPSOID = WGS-84 PROJ_LENGTH_UNIT = cm PROJ_MEAN_RADIUS = authalic PROJ_SCALE_FACTOR = default # # PostScript Parameters # PS_CHAR_ENCODING = ISOLatin1+ PS_COLOR_MODEL = rgb PS_COMMENTS = false PS_IMAGE_COMPRESS = deflate,5 PS_LINE_CAP = butt PS_LINE_JOIN = miter PS_MITER_LIMIT = 35 PS_MEDIA = a4 PS_PAGE_COLOR = white PS_PAGE_ORIENTATION = portrait PS_SCALE_X = 1 PS_SCALE_Y = 1 PS_TRANSPARENCY = Normal # # Calendar/Time Parameters # TIME_EPOCH = 1970-01-01T00:00:00 TIME_IS_INTERVAL = off TIME_INTERVAL_FRACTION = 0.5 TIME_UNIT = s TIME_WEEK_START = Monday TIME_Y2K_OFFSET_YEAR = 1950 '''
args = [gmtdefaultsbinary]
environ = os.environ.copy() environ['GMTHOME'] = gmthomedir or ''
p = subprocess.Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=environ)
(stdout, stderr) = p.communicate() m = re.search(br'(\d+(\.\d+)*)', stderr) \ or re.search(br'# GMT (\d+(\.\d+)*)', stdout)
if not m: raise GMTInstallationProblem( "Can't extract version number from output of %s" % gmtdefaultsbinary)
return str(m.group(1).decode('ascii'))
installations = {} errmesses = []
# GMT 4.x: try: p = subprocess.Popen( ['GMT'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdout, stderr) = p.communicate()
m = re.search(br'Version\s+(\d+(\.\d+)*)', stderr, re.M) if not m: raise GMTInstallationProblem( "Can't version number from output of GMT")
version = str(m.group(1).decode('ascii')) if version[0] != '5':
m = re.search(br'^\s+executables\s+(.+)$', stderr, re.M) if not m: raise GMTInstallationProblem( "Can't extract executables dir from output of GMT")
gmtbin = str(m.group(1).decode('ascii'))
m = re.search(br'^\s+shared data\s+(.+)$', stderr, re.M) if not m: raise GMTInstallationProblem( "Can't extract shared dir from output of GMT")
gmtshare = str(m.group(1).decode('ascii')) if not gmtshare.endswith('/share'): raise GMTInstallationProblem( "Can't determine GMTHOME from output of GMT")
gmthome = gmtshare[:-6]
installations[version] = { 'home': gmthome, 'bin': gmtbin}
except OSError as e: errmesses.append(('GMT', str(e)))
try: version = str(subprocess.check_output( ['gmt', '--version']).strip().decode('ascii')).split('_')[0] gmtbin = str(subprocess.check_output( ['gmt', '--show-bindir']).strip().decode('ascii')) installations[version] = { 'bin': gmtbin}
except (OSError, subprocess.CalledProcessError) as e: errmesses.append(('gmt', str(e)))
if not installations: s = [] for (progname, errmess) in errmesses: s.append('Cannot start "%s" executable: %s' % (progname, errmess))
raise GMTInstallationProblem(', '.join(s))
return installations
avails = sorted(_gmt_defaults_by_version.keys(), key=key_version) for iavail, avail in enumerate(avails): if key_version(version) == key_version(avail): return version
elif key_version(version) < key_version(avail): return avails[max(0, iavail-1)]
return avails[-1]
'''Get default GMT configuration dict for given version.'''
xversion = appropriate_defaults_version(version)
# if not version in _gmt_defaults_by_version: # raise GMTError('No GMT defaults for version %s found' % version)
gmt_defaults = _gmt_defaults_by_version[xversion]
d = {} for line in gmt_defaults.splitlines(): sline = line.strip() if not sline or sline.startswith('#'): continue
k, v = sline.split('=', 1) d[k.strip()] = v.strip()
return d
d1 = gmt_default_config(v1) d2 = gmt_default_config(v2) for k in d1: if k not in d2: print('%s not in %s' % (k, v2)) else: if d1[k] != d2[k]: print('%s %s = %s' % (v1, k, d1[k])) print('%s %s = %s' % (v2, k, d2[k]))
for k in d2: if k not in d1: print('%s not in %s' % (k, v1))
# diff_defaults('4.5.2', '4.5.3')
home_dir = installation.get('home', None) bin_dir = installation['bin'] version = installation['version']
for d in home_dir, bin_dir: if d is not None: if not os.path.exists(d): logging.error(('Directory does not exist: %s\n' 'Check your GMT installation.') % d)
if version[0] != '5': gmtdefaults = pjoin(bin_dir, 'gmtdefaults')
versionfound = get_gmt_version(gmtdefaults, home_dir)
if versionfound != version: raise GMTInstallationProblem(( 'Expected GMT version %s but found version %s.\n' '(Looking at output of %s)') % ( version, versionfound, gmtdefaults))
setup_gmt_installations() if version != 'newest' and version not in _gmt_installations: logging.warn('GMT version %s not installed, taking version %s instead' % (version, newest_installed_gmt_version()))
version = 'newest'
if version == 'newest': version = newest_installed_gmt_version()
installation = dict(_gmt_installations[version])
return installation
if not setup_gmt_installations.have_done: if not _gmt_installations:
_gmt_installations.update(detect_gmt_installations())
# store defaults as dicts into the gmt installations dicts for version, installation in _gmt_installations.items(): installation['defaults'] = gmt_default_config(version) installation['version'] = version
for installation in _gmt_installations.values(): check_gmt_installation(installation)
setup_gmt_installations.have_done = True
A1 1684 2380 A2 1190 1684 A3 842 1190 A4 595 842 A5 421 595 A6 297 421 A7 210 297 A8 148 210 A9 105 148 A10 74 105 B0 2836 4008 B1 2004 2836 B2 1418 2004 B3 1002 1418 B4 709 1002 B5 501 709 archA 648 864 archB 864 1296 archC 1296 1728 archD 1728 2592 archE 2592 3456 flsa 612 936 halfletter 396 612 note 540 720 letter 612 792 legal 612 1008 11x17 792 1224 ledger 1224 792'''
if not _paper_sizes: for line in _paper_sizes_a.splitlines(): k, w, h = line.split() _paper_sizes[k.lower()] = float(w), float(h)
setup_paper_sizes() return _paper_sizes[k.lower().rstrip('+')]
setup_paper_sizes() return _paper_sizes
for k in ['MEASURE_UNIT', 'PROJ_LENGTH_UNIT']: if k in gmt_config: return gmt_config[k]
raise GmtPyError('cannot get measure unit / proj length unit from config')
for k in ['PAPER_MEDIA', 'PS_MEDIA']: if k in gmt_config: return gmt_config[k]
raise GmtPyError('cannot get paper media from config')
for k in ['PAGE_ORIENTATION', 'PS_PAGE_ORIENTATION']: if k in gmt_config: return gmt_config[k]
raise GmtPyError('cannot get paper orientation from config')
leftmargin, topmargin, rightmargin, bottommargin = margins portrait = page_orientation(gmt_config).lower() == 'portrait'
paper_size = get_paper_size(paper_media(gmt_config)) if not portrait: paper_size = paper_size[1], paper_size[0]
xoffset = (paper_size[0] - (width + leftmargin + rightmargin)) / \ 2.0 + leftmargin yoffset = (paper_size[1] - (height + topmargin + bottommargin)) / \ 2.0 + bottommargin
if portrait: bb1 = int((xoffset - leftmargin)) bb2 = int((yoffset - bottommargin)) bb3 = bb1 + int((width+leftmargin+rightmargin)) bb4 = bb2 + int((height+topmargin+bottommargin)) else: bb1 = int((yoffset - topmargin)) bb2 = int((xoffset - leftmargin)) bb3 = bb1 + int((height+topmargin+bottommargin)) bb4 = bb2 + int((width+leftmargin+rightmargin))
return xoffset, yoffset, (bb1, bb2, bb3, bb4)
'''Get the built-in gmtdefaults.'''
if version not in _gmt_installations: logging.warn('GMT version %s not installed, taking version %s instead' % (version, newest_installed_gmt_version())) version = 'newest'
if version == 'newest': version = newest_installed_gmt_version()
return _gmt_defaults_by_version[version]
'''Write COARDS compliant netcdf (grd) file.'''
assert y.size, x.size == z.shape ny, nx = z.shape nc = netcdf.netcdf_file(filename, 'w') assert naming in ('xy', 'lonlat')
if naming == 'xy': kx, ky = 'x', 'y' else: kx, ky = 'lon', 'lat'
nc.node_offset = 0 if title is not None: nc.title = title
nc.Conventions = 'COARDS/CF-1.0' nc.createDimension(kx, nx) nc.createDimension(ky, ny)
xvar = nc.createVariable(kx, 'd', (kx,)) yvar = nc.createVariable(ky, 'd', (ky,)) if naming == 'xy': xvar.long_name = kx yvar.long_name = ky else: xvar.long_name = 'longitude' xvar.units = 'degrees_east' yvar.long_name = 'latitude' yvar.units = 'degrees_north'
zvar = nc.createVariable('z', 'd', (ky, kx))
xvar[:] = x.astype(num.float64) yvar[:] = y.astype(num.float64) zvar[:] = z.astype(num.float64)
nc.close()
arr = var[:].copy() if hasattr(var, 'scale_factor'): arr *= var.scale_factor
if hasattr(var, 'add_offset'): arr += var.add_offset
return arr
'''Read COARDS compliant netcdf (grd) file.'''
nc = netcdf.netcdf_file(filename, 'r') vkeys = list(nc.variables.keys()) kx = 'x' ky = 'y' if 'lon' in vkeys: kx = 'lon' if 'lat' in vkeys: ky = 'lat'
x = to_array(nc.variables[kx]) y = to_array(nc.variables[ky]) z = to_array(nc.variables['z'])
nc.close() return x, y, z
return (asorted[1:] + asorted[:-1])/2.
eps = (asorted[-1]-asorted[0])/asorted.size return num.sum(asorted[1:] - asorted[:-1] >= eps) + 1
eps = (asorted[-1]-asorted[0])/asorted.size indis = num.nonzero(asorted[1:] - asorted[:-1] >= eps)[0] indis = num.concatenate((num.array([0]), indis+1, num.array([asorted.size]))) asum = num.zeros(asorted.size+1) asum[1:] = num.cumsum(asorted) return (asum[indis[1:]] - asum[indis[:-1]]) / (indis[1:]-indis[:-1])
indis = num.nonzero(asorted[1:] - asorted[:-1])[0] indis = num.concatenate((num.array([0]), indis+1, num.array([asorted.size]))) bsum = num.zeros(b.size+1) bsum[1:] = num.cumsum(b) return ( asorted[indis[:-1]], (bsum[indis[1:]] - bsum[indis[:-1]]) / (indis[1:]-indis[:-1]))
nx, ny = xvals.size, yvals.size xindi = num.digitize(x, centers_to_edges(xvals)) yindi = num.digitize(y, centers_to_edges(yvals))
zindi = yindi*nx+xindi order = num.argsort(zindi) z = z[order] zindi = zindi[order]
zindi, z = blockmean(zindi, z) znew = num.empty(nx*ny, dtype=num.float) znew[:] = num.nan znew[zindi] = z return znew.reshape(ny, nx)
critical_fraction = 1./num.e - 0.014*3 xs = x_sorted ys = y_sorted nxs, nys = nvals(xs), nvals(ys) if mode == 'nonrandom': return nxs, nys, 0 elif xs.size == nxs*nys: # exact match return nxs, nys, 0 elif nxs >= xs.size*critical_fraction and nys >= xs.size*critical_fraction: # possibly randomly sampled nxs = int(math.sqrt(xs.size)) nys = nxs return nxs, nys, 2 else: return nxs, nys, 1
'''Grid tabular XYZ data by binning.
This function does some extra work to guess the size of the grid. This should work fine if the input values are already defined on an rectilinear grid, even if data points are missing or duplicated. This routine also tries to detect a random distribution of input data and in that case creates a grid of size sqrt(N) x sqrt(N).
The points do not have to be given in any particular order. Grid nodes without data are assigned the NaN value. If multiple data points map to the same grid node, their average is assigned to the grid node. '''
x, y, z = [num.asarray(X) for X in (x, y, z)] assert x.size == y.size == z.size xs, ys = num.sort(x), num.sort(y) nx, ny, badness = guess_field_size(xs, ys, z, mode=mode) if badness <= 1: xf = guess_vals(xs) yf = guess_vals(ys) zf = griddata_regular(x, y, z, xf, yf) else: xf = num.linspace(xs[0], xs[-1], nx) yf = num.linspace(ys[0], ys[-1], ny) zf = griddata_regular(x, y, z, xf, yf)
return xf, yf, zf
assert yf.size, xf.size == zf.shape x = num.tile(xf, yf.size) y = num.repeat(yf, xf.size) z = zf.flatten() return x, y, z
a2 = num.empty(a.size*2-1) a2[::2] = a a2[1::2] = (a[:-1] + a[1:])/2. return a2
f2 = num.empty((f.shape[0]*2-1, f.shape[1]*2-1)) f2[:, :] = num.nan f2[::2, ::2] = f f2[1::2, ::2] = (f[:-1, :] + f[1:, :])/2. f2[::2, 1::2] = (f[:, :-1] + f[:, 1:])/2. f2[1::2, 1::2] = (f[:-1, :-1] + f[1:, :-1] + f[:-1, 1:] + f[1:, 1:])/4. diag = f2[1::2, 1::2] diagA = (f[:-1, :-1] + f[1:, 1:]) / 2. diagB = (f[1:, :-1] + f[:-1, 1:]) / 2. f2[1::2, 1::2] = num.where(num.isnan(diag), diagA, diag) f2[1::2, 1::2] = num.where(num.isnan(diag), diagB, diag) return f2
x2 = double1d(x) y2 = double1d(y) z2 = double2d(z) return x2, y2, z2
'''Abstract base class providing template interpolation, accessible as attributes.
Classes deriving from this one, have to implement a :py:meth:`get_params` method, which is called to get a dict to do ordinary ``"%(key)x"``-substitutions. The deriving class must also provide a dict with the templates.'''
self.templates = {}
params = self.get_params(**kwargs) strings = [t % params for t in templates] return strings
# hand through templates dict return self.templates[template_name]
self.templates[template_name] = template
return template_name in self.templates
return iter(self.templates)
return len(self.templates)
del(self.templates[template_name])
templates = [self.templates[n] for n in template_names] return self.fill(templates, **kwargs)
if [n for n in template_names if n not in self.templates]: raise AttributeError(template_names)
def f(**kwargs): return self._simple_fill(template_names, **kwargs)
return f
'''Round ``x`` to nice value.'''
exp = 1.0 sign = 1 if x < 0.0: x = -x sign = -1 while x >= 1.0: x /= 10.0 exp *= 10.0 while x < 0.1: x *= 10.0 exp /= 10.0
if x >= 0.75: return sign * 1.0 * exp if x >= 0.375: return sign * 0.5 * exp if x >= 0.225: return sign * 0.25 * exp if x >= 0.15: return sign * 0.2 * exp
return sign * 0.1 * exp
'''Tunable 1D autoscaling based on data range.
Instances of this class may be used to determine nice minima, maxima and increments for ax annotations, as well as suitable common exponents for notation.
The autoscaling process is guided by the following public attributes:
.. py:attribute:: approx_ticks
Approximate number of increment steps (tickmarks) to generate.
.. py:attribute:: mode
Mode of operation: one of ``'auto'``, ``'min-max'``, ``'0-max'``, ``'min-0'``, ``'symmetric'`` or ``'off'``.
================ ================================================== mode description ================ ================================================== ``'auto'``: Look at data range and choose one of the choices below. ``'min-max'``: Output range is selected to include data range. ``'0-max'``: Output range shall start at zero and end at data max. ``'min-0'``: Output range shall start at data min and end at zero. ``'symmetric'``: Output range shall by symmetric by zero. ``'off'``: Similar to ``'min-max'``, but snap and space are disabled, such that the output range always exactly matches the data range. ================ ==================================================
.. py:attribute:: exp
If defined, override automatically determined exponent for notation by the given value.
.. py:attribute:: snap
If set to True, snap output range to multiples of increment. This parameter has no effect, if mode is set to ``'off'``.
.. py:attribute:: inc
If defined, override automatically determined tick increment by the given value.
.. py:attribute:: space
Add some padding to the range. The value given, is the fraction by which the output range is increased on each side. If mode is ``'0-max'`` or ``'min-0'``, the end at zero is kept fixed at zero. This parameter has no effect if mode is set to ``'off'``.
.. py:attribute:: exp_factor
Exponent of notation is chosen to be a multiple of this value.
.. py:attribute:: no_exp_interval:
Range of exponent, for which no exponential notation is allowed.
'''
self, approx_ticks=7.0, mode='auto', exp=None, snap=False, inc=None, space=0.0, exp_factor=3, no_exp_interval=(-3, 5)):
'''Create new AutoScaler instance.
The parameters are described in the AutoScaler documentation. '''
self.approx_ticks = approx_ticks self.mode = mode self.exp = exp self.snap = snap self.inc = inc self.space = space self.exp_factor = exp_factor self.no_exp_interval = no_exp_interval
'''Get nice minimum, maximum and increment for given data range.
Returns ``(minimum, maximum, increment)`` or ``(maximum, minimum, -increment)``, depending on whether data_range is ``(data_min, data_max)`` or ``(data_max, data_min)``. If ``override_mode`` is defined, the mode attribute is temporarily overridden by the given value. '''
data_min = min(data_range) data_max = max(data_range)
is_reverse = (data_range[0] > data_range[1])
a = self.mode if self.mode == 'auto': a = self.guess_autoscale_mode(data_min, data_max)
if override_mode is not None: a = override_mode
mi, ma = 0, 0 if a == 'off': mi, ma = data_min, data_max elif a == '0-max': mi = 0.0 if data_max > 0.0: ma = data_max else: ma = 1.0 elif a == 'min-0': ma = 0.0 if data_min < 0.0: mi = data_min else: mi = -1.0 elif a == 'min-max': mi, ma = data_min, data_max elif a == 'symmetric': m = max(abs(data_min), abs(data_max)) mi = -m ma = m
nmi = mi if (mi != 0. or a == 'min-max') and a != 'off': nmi = mi - self.space*(ma-mi)
nma = ma if (ma != 0. or a == 'min-max') and a != 'off': nma = ma + self.space*(ma-mi)
mi, ma = nmi, nma
if mi == ma and a != 'off': mi -= 1.0 ma += 1.0
# make nice tick increment if self.inc is not None: inc = self.inc else: if self.approx_ticks > 0.: inc = nice_value((ma-mi) / self.approx_ticks) else: inc = nice_value((ma-mi)*10.)
if inc == 0.0: inc = 1.0
# snap min and max to ticks if this is wanted if self.snap and a != 'off': ma = inc * math.ceil(ma/inc) mi = inc * math.floor(mi/inc)
if is_reverse: return ma, mi, -inc else: return mi, ma, inc
'''Get nice exponent for notation of ``x``.
For ax annotations, give tick increment as ``x``.'''
if self.exp is not None: return self.exp
x = abs(x) if x == 0.0: return 0
if 10**self.no_exp_interval[0] <= x <= 10**self.no_exp_interval[1]: return 0
return math.floor(math.log10(x)/self.exp_factor)*self.exp_factor
'''Guess mode of operation, based on data range.
Used to map ``'auto'`` mode to ``'0-max'``, ``'min-0'``, ``'min-max'`` or ``'symmetric'``.'''
a = 'min-max' if data_min >= 0.0: if data_min < data_max/2.: a = '0-max' else: a = 'min-max' if data_max <= 0.0: if data_max > data_min/2.: a = 'min-0' else: a = 'min-max' if data_min < 0.0 and data_max > 0.0: if abs((abs(data_max)-abs(data_min)) / (abs(data_max)+abs(data_min))) < 0.5: a = 'symmetric' else: a = 'min-max' return a
'''Ax description with autoscaling capabilities.
The ax is described by the :py:class:`AutoScaler` public attributes, plus the following additional attributes (with default values given in paranthesis):
.. py:attribute:: label
Ax label (without unit).
.. py:attribute:: unit
Physical unit of the data attached to this ax.
.. py:attribute:: scaled_unit
(see below)
.. py:attribute:: scaled_unit_factor
Scaled physical unit and factor between unit and scaled_unit so that
unit = scaled_unit_factor x scaled_unit.
(E.g. if unit is 'm' and data is in the range of nanometers, you may want to set the scaled_unit to 'nm' and the scaled_unit_factor to 1e9.)
.. py:attribute:: limits
If defined, fix range of ax to limits=(min,max).
.. py:attribute:: masking
If true and if there is a limit on the ax, while calculating ranges, the data points are masked such that data points outside of this axes limits are not used to determine the range of another dependant ax.
'''
scaled_unit='', limits=None, masking=True, **kwargs):
AutoScaler.__init__(self, **kwargs) self.label = label self.unit = unit self.scaled_unit_factor = scaled_unit_factor self.scaled_unit = scaled_unit self.limits = limits self.masking = masking
'''Get label string including the unit and multiplier.'''
slabel, sunit, sexp = '', '', '' if self.label: slabel = self.label
if unit or exp != 0: if exp != 0: sexp = '\\327 10@+%i@+' % exp sunit = '[ %s %s ]' % (sexp, unit) else: sunit = '[ %s ]' % unit
p = [] if slabel: p.append(slabel)
if sunit: p.append(sunit)
return ' '.join(p)
override_scaled_unit_factor=None):
'''Get minimum, maximum, increment and label string for ax display.'
Returns minimum, maximum, increment and label string including unit and multiplier for given data range.
If ``ax_projection`` is True, values suitable to be displayed on the ax are returned, e.g. min, max and inc are returned in scaled units. Otherwise the values are returned in the original units, without any scaling applied. '''
sf = self.scaled_unit_factor
if override_scaled_unit_factor is not None: sf = override_scaled_unit_factor
dr_scaled = [sf*x for x in data_range]
mi, ma, inc = self.make_scale(dr_scaled, override_mode=override_mode) if self.inc is not None: inc = self.inc*sf
if ax_projection: exp = self.make_exp(inc) if sf == 1. and override_scaled_unit_factor is None: unit = self.unit else: unit = self.scaled_unit label = self.label_str(exp, unit) return mi/10**exp, ma/10**exp, inc/10**exp, label else: label = self.label_str(0, self.unit) return mi/sf, ma/sf, inc/sf, label
'''2D/3D autoscaling and ax annotation facility.
Instances of this class provide automatic determination of plot ranges, tick increments and scaled annotations, as well as label/unit handling. It can in particular be used to automatically generate the -R and -B option arguments, which are required for most GMT commands.
It extends the functionality of the :py:class:`Ax` and :py:class:`AutoScaler` classes at the level, where it can not be handled anymore by looking at a single dimension of the dataset's data, e.g.:
* The ability to impose a fixed aspect ratio between two axes.
* Recalculation of data range on non-limited axes, when there are limits imposed on other axes.
'''
percent_interval=None, copy_from=None):
Guru.__init__(self)
if copy_from: self.templates = copy.deepcopy(copy_from.templates) self.axes = copy.deepcopy(copy_from.axes) self.data_ranges = copy.deepcopy(copy_from.data_ranges) self.aspect = copy_from.aspect
if percent_interval is not None: from scipy.stats import scoreatpercentile as scap
self.templates = dict( R='-R%(xmin)g/%(xmax)g/%(ymin)g/%(ymax)g', B='-B%(xinc)g:%(xlabel)s:/%(yinc)g:%(ylabel)s:WSen', T='-T%(zmin)g/%(zmax)g/%(zinc)g')
maxdim = 2 if data_tuples: maxdim = max(maxdim, max([len(dt) for dt in data_tuples])) else: if axes: maxdim = len(axes) data_tuples = [([],) * maxdim] if axes is not None: self.axes = axes else: self.axes = [Ax() for i in range(maxdim)]
# sophisticated data-range calculation data_ranges = [None] * maxdim for dt_ in data_tuples: dt = num.asarray(dt_) in_range = True for ax, x in zip(self.axes, dt): if ax.limits and ax.masking: ax_limits = list(ax.limits) if ax_limits[0] is None: ax_limits[0] = -num.inf if ax_limits[1] is None: ax_limits[1] = num.inf in_range = num.logical_and( in_range, num.logical_and(ax_limits[0] <= x, x <= ax_limits[1]))
for i, ax, x in zip(range(maxdim), self.axes, dt):
if not ax.limits or None in ax.limits: if len(x) >= 1: if in_range is not True: xmasked = num.where(in_range, x, num.NaN) if percent_interval is None: range_this = ( num.nanmin(xmasked), num.nanmax(xmasked)) else: xmasked_finite = num.compress( num.isfinite(xmasked), xmasked) range_this = ( scap(xmasked_finite, (100.-percent_interval)/2.), scap(xmasked_finite, 100.-(100.-percent_interval)/2.)) else: if percent_interval is None: range_this = num.nanmin(x), num.nanmax(x) else: xmasked_finite = num.compress( num.isfinite(xmasked), xmasked) range_this = ( scap(xmasked_finite, (100.-percent_interval)/2.), scap(xmasked_finite, 100.-(100.-percent_interval)/2.)) else: range_this = (0., 1.)
if ax.limits: if ax.limits[0] is not None: range_this = ax.limits[0], max(ax.limits[0], range_this[1])
if ax.limits[1] is not None: range_this = min(ax.limits[1], range_this[0]), ax.limits[1]
else: range_this = ax.limits
if data_ranges[i] is None and range_this[0] <= range_this[1]: data_ranges[i] = range_this else: mi, ma = range_this if data_ranges[i] is not None: mi = min(data_ranges[i][0], mi) ma = max(data_ranges[i][1], ma)
data_ranges[i] = (mi, ma)
for i in range(len(data_ranges)): if data_ranges[i] is None or not ( num.isfinite(data_ranges[i][0]) and num.isfinite(data_ranges[i][1])):
data_ranges[i] = (0., 1.)
self.data_ranges = data_ranges self.aspect = aspect
return ScaleGuru(copy_from=self)
'''Get dict with output parameters.
For each data dimension, ax minimum, maximum, increment and a label string (including unit and exponential factor) are determined. E.g. in for the first dimension the output dict will contain the keys ``'xmin'``, ``'xmax'``, ``'xinc'``, and ``'xlabel'``.
Normally, values corresponding to the scaling of the raw data are produced, but if ``ax_projection`` is ``True``, values which are suitable to be printed on the axes are returned. This means that in the latter case, the :py:attr:`Ax.scaled_unit` and :py:attr:`Ax.scaled_unit_factor` attributes as set on the axes are respected and that a common 10^x factor is factored out and put to the label string. '''
xmi, xma, xinc, xlabel = self.axes[0].make_params( self.data_ranges[0], ax_projection) ymi, yma, yinc, ylabel = self.axes[1].make_params( self.data_ranges[1], ax_projection) if len(self.axes) > 2: zmi, zma, zinc, zlabel = self.axes[2].make_params( self.data_ranges[2], ax_projection)
# enforce certain aspect, if needed if self.aspect is not None: xwid = xma-xmi ywid = yma-ymi if ywid < xwid*self.aspect: ymi -= (xwid*self.aspect - ywid)*0.5 yma += (xwid*self.aspect - ywid)*0.5 ymi, yma, yinc, ylabel = self.axes[1].make_params( (ymi, yma), ax_projection, override_mode='off', override_scaled_unit_factor=1.)
elif xwid < ywid/self.aspect: xmi -= (ywid/self.aspect - xwid)*0.5 xma += (ywid/self.aspect - xwid)*0.5 xmi, xma, xinc, xlabel = self.axes[0].make_params( (xmi, xma), ax_projection, override_mode='off', override_scaled_unit_factor=1.)
params = dict(xmin=xmi, xmax=xma, xinc=xinc, xlabel=xlabel, ymin=ymi, ymax=yma, yinc=yinc, ylabel=ylabel) if len(self.axes) > 2: params.update(dict(zmin=zmi, zmax=zma, zinc=zinc, zlabel=zlabel))
return params
'''Sizing policy implementing a minimal size, plus a desire to grow.'''
self.minimal = minimal if grow is None: if minimal is None: self.grow = 1.0 else: self.grow = 0.0 else: self.grow = grow self.value = 1.0
if self.minimal is not None: return self.minimal else: return 0.0
return self.grow
self.value = value
return self.value
sizes = list(sizes) gsum = sum(grows) if gsum > 0.0: for i in range(len(sizes)): sizes[i] += space*grows[i]/gsum return sizes
'''Base class of the gmtpy layout system.
The Widget class provides the basic functionality for the nesting and placing of elements on the output page, and maintains the sizing policies of each element. Each of the layouts defined in gmtpy is itself a Widget.
Sizing of the widget is controlled by :py:meth:`get_min_size` and :py:meth:`get_grow` which should be overloaded in derived classes. The basic behaviour of a Widget instance is to have a vertical and a horizontal minimum size which default to zero, as well as a vertical and a horizontal desire to grow, represented by floats, which default to 1.0. Additionally an aspect ratio constraint may be imposed on the Widget.
After layouting, the widget provides its width, height, x-offset and y-offset in various ways. Via the Guru interface (see :py:class:`Guru` class), templates for the -X, -Y and -J option arguments used by GMT arguments are provided. The defaults are suitable for plotting of linear (-JX) plots. Other projections can be selected by giving an appropriate 'J' template, or by manual construction of the -J option, e.g. by utilizing the :py:meth:`width` and :py:meth:`height` methods. The :py:meth:`bbox` method can be used to create a PostScript bounding box from the widgets border, e.g. for use in the :py:meth:`save` method of :py:class:`GMT` instances.
The convention is, that all sizes are given in PostScript points. Conversion factors are provided as constants :py:const:`inch` and :py:const:`cm` in the gmtpy module. '''
'''Create new widget.'''
Guru.__init__(self)
self.templates = dict( X='-Xa%(xoffset)gp', Y='-Ya%(yoffset)gp', J='-JX%(width)gp/%(height)gp')
if horizontal is None: self.horizontal = GumSpring() else: self.horizontal = horizontal
if vertical is None: self.vertical = GumSpring() else: self.vertical = vertical
self.aspect = None self.parent = parent self.dirty = True
'''Set the parent widget.
This method should not be called directly. The :py:meth:`set_widget` methods are responsible for calling this. '''
self.parent = parent self.dirtyfy()
'''Get the widgets parent widget.'''
return self.parent
'''Get the root widget in the layout hierarchy.'''
if self.parent is not None: return self.get_parent() else: return self
'''Set the horizontal sizing policy of the Widget.
:param minimal: new minimal width of the widget :param grow: new horizontal grow disire of the widget '''
self.horizontal = GumSpring(minimal, grow) self.dirtyfy()
return self.horizontal.get_minimal(), self.horizontal.get_grow()
'''Set the horizontal sizing policy of the Widget.
:param minimal: new minimal height of the widget :param grow: new vertical grow disire of the widget '''
self.vertical = GumSpring(minimal, grow) self.dirtyfy()
return self.vertical.get_minimal(), self.vertical.get_grow()
'''Set aspect constraint on the widget.
The aspect is given as height divided by width. '''
self.aspect = aspect self.dirtyfy()
'''Shortcut to set sizing and aspect constraints in a single method call.'''
self.set_horizontal(minimal[0], grow[0]) self.set_vertical(minimal[1], grow[1]) self.set_aspect(aspect)
mh, gh = self.get_horizontal() mv, gv = self.get_vertical() return (mh, mv), (gh, gv), self.aspect
'''Get legal size for widget.
Returns: (new_size, new_offset)
Given a box as ``size`` and ``offset``, return ``new_size`` and ``new_offset``, such that the widget's sizing and aspect constraints are fullfilled. The returned box is centered on the given input box. '''
sh, sv = size oh, ov = offset shs, svs = Widget.get_min_size(self) ghs, gvs = Widget.get_grow(self)
if ghs == 0.0: oh += (sh-shs)/2. sh = shs
if gvs == 0.0: ov += (sv-svs)/2. sv = svs
if self.aspect is not None: if sh > sv/self.aspect: oh += (sh-sv/self.aspect)/2. sh = sv/self.aspect if sv > sh*self.aspect: ov += (sv-sh*self.aspect)/2. sv = sh*self.aspect
return (sh, sv), (oh, ov)
'''Get minimum size of widget.
Used by the layout managers. Should be overloaded in derived classes. '''
mh, mv = self.horizontal.get_minimal(), self.vertical.get_minimal() if self.aspect is not None: if mv == 0.0: return mh, mh*self.aspect elif mh == 0.0: return mv/self.aspect, mv return mh, mv
'''Get widget's desire to grow.
Used by the layout managers. Should be overloaded in derived classes. '''
return self.horizontal.get_grow(), self.vertical.get_grow()
'''Set the widget's current size.
Should not be called directly. It is the layout manager's responsibility to call this. '''
(sh, sv), inner_offset = self.legalize(size, offset) self.offset = inner_offset self.horizontal.set_value(sh) self.vertical.set_value(sv) self.dirty = False
def __str__(self):
def indent(ind, str): return ('\n'+ind).join(str.splitlines()) size, offset = self.get_size() s = "%s (%g x %g) (%g, %g)\n" % ((self.__class__,) + size + offset) children = self.get_children() if children: s += '\n'.join([' ' + indent(' ', str(c)) for c in children]) return s
def indent(ind, str): return ('\n'+ind).join(str.splitlines()) mins, grows, aspect = self.get_policy() s = "%s: minimum=(%s, %s), grow=(%s, %s), aspect=%s\n" % ( (self.__class__,) + mins+grows+(aspect,))
children = self.get_children() if children: s += '\n'.join([' ' + indent( ' ', c.policies_debug_str()) for c in children]) return s
'''Get coordinates of the corners of the widget.
Returns list with coordinate tuples.
If ``descend`` is True, the returned list will contain corner coordinates of all sub-widgets. '''
self.do_layout() (sh, sv), (oh, ov) = self.get_size() corners = [(oh, ov), (oh+sh, ov), (oh+sh, ov+sv), (oh, ov+sv)] if descend: for child in self.get_children(): corners.extend(child.get_corners(descend=True)) return corners
'''Get sizes of this widget and all it's children.
Returns a list with size tuples. ''' self.do_layout() sizes = [self.get_size()] for child in self.get_children(): sizes.extend(child.get_sizes()) return sizes
'''Triggers layouting of the widget hierarchy, if needed.'''
if self.parent is not None: return self.parent.do_layout()
if not self.dirty: return
sh, sv = self.get_min_size() gh, gv = self.get_grow() if sh == 0.0 and gh != 0.0: sh = 15.*cm if sv == 0.0 and gv != 0.0: sv = 15.*cm*gv/gh * 1./golden_ratio self.set_size((sh, sv), (0., 0.))
'''Get sub-widgets contained in this widget.
Returns a list of widgets. '''
return []
'''Get current size and position of the widget.
Triggers layouting and returns ``((width, height), (xoffset, yoffset))`` '''
self.do_layout() return (self.horizontal.get_value(), self.vertical.get_value()), self.offset
'''Get current size and position of the widget.
Triggers layouting and returns dict with keys ``'xoffset'``, ``'yoffset'``, ``'width'`` and ``'height'``. '''
self.do_layout() (w, h), (xo, yo) = self.get_size() return dict(xoffset=xo, yoffset=yo, width=w, height=h, width_m=w/_units['m'])
'''Get current width of the widget.
Triggers layouting and returns width.'''
self.do_layout() return self.horizontal.get_value()
'''Get current height of the widget.
Triggers layouting and return height.'''
self.do_layout() return self.vertical.get_value()
'''Get PostScript bounding box for this widget.
Triggers layouting and returns values suitable to create PS bounding box, representing the widgets current size and position. '''
self.do_layout() return (self.offset[0], self.offset[1], self.offset[0]+self.width(), self.offset[1]+self.height())
'''Set dirty flag on top level widget in the hierarchy.
Called by various methods, to indicate, that the widget hierarchy needs new layouting. '''
if self.parent is not None: self.parent.dirtyfy()
self.dirty = True
'''A layout manager which centers its single child widget.
The child widget may be oversized. '''
Widget.__init__(self, horizontal, vertical) self.content = Widget(horizontal=GumSpring(grow=1.), vertical=GumSpring(grow=1.), parent=self)
shs, svs = Widget.get_min_size(self) sh, sv = self.content.get_min_size() return max(shs, sh), max(svs, sv)
ghs, gvs = Widget.get_grow(self) gh, gv = self.content.get_grow() return gh*ghs, gv*gvs
(sh, sv), (oh, ov) = self.legalize(size, offset)
shc, svc = self.content.get_min_size() ghc, gvc = self.content.get_grow() if ghc != 0.: shc = sh if gvc != 0.: svc = sv ohc = oh+(sh-shc)/2. ovc = ov+(sv-svc)/2.
self.content.set_size((shc, svc), (ohc, ovc)) Widget.set_size(self, (sh, sv), (oh, ov))
'''Set the child widget, which shall be centered.'''
if widget is None: widget = Widget()
self.content = widget
widget.set_parent(self)
return self.content
return [self.content]
'''A layout manager containing a center widget sorrounded by four margin widgets.
::
+---------------------------+ | top | +---------------------------+ | | | | | left | center | right | | | | | +---------------------------+ | bottom | +---------------------------+
This layout manager does a little bit of extra effort to maintain the aspect constraint of the center widget, if this is set. It does so, by allowing for a bit more flexibility in the sizing of the margins. Two shortcut methods are provided to set the margin sizes in one shot: :py:meth:`set_fixed_margins` and :py:meth:`set_min_margins`. The first sets the margins to fixed sizes, while the second gives them a minimal size and a (neglectably) small desire to grow. Using the latter may be useful when setting an aspect constraint on the center widget, because this way the maximum size of the center widget may be controlled without creating empty spaces between the widgets. '''
Widget.__init__(self, horizontal, vertical) mw = 3.*cm self.left = Widget( horizontal=GumSpring(grow=0.15, minimal=mw), parent=self) self.right = Widget( horizontal=GumSpring(grow=0.15, minimal=mw), parent=self) self.top = Widget( vertical=GumSpring(grow=0.15, minimal=mw/golden_ratio), parent=self) self.bottom = Widget( vertical=GumSpring(grow=0.15, minimal=mw/golden_ratio), parent=self) self.center = Widget( horizontal=GumSpring(grow=0.7), vertical=GumSpring(grow=0.7), parent=self)
'''Give margins fixed size constraints.'''
self.left.set_horizontal(left, 0) self.right.set_horizontal(right, 0) self.top.set_vertical(top, 0) self.bottom.set_vertical(bottom, 0)
'''Give margins a minimal size and the possibility to grow.
The desire to grow is set to a very small number.''' self.left.set_horizontal(left, grow) self.right.set_horizontal(right, grow) self.top.set_vertical(top, grow) self.bottom.set_vertical(bottom, grow)
shs, svs = Widget.get_min_size(self)
sl, sr, st, sb, sc = [x.get_min_size() for x in ( self.left, self.right, self.top, self.bottom, self.center)] gl, gr, gt, gb, gc = [x.get_grow() for x in ( self.left, self.right, self.top, self.bottom, self.center)]
shsum = sl[0]+sr[0]+sc[0] svsum = st[1]+sb[1]+sc[1]
# prevent widgets from collapsing for s, g in ((sl, gl), (sr, gr), (sc, gc)): if s[0] == 0.0 and g[0] != 0.0: shsum += 0.1*cm
for s, g in ((st, gt), (sb, gb), (sc, gc)): if s[1] == 0.0 and g[1] != 0.0: svsum += 0.1*cm
sh = max(shs, shsum) sv = max(svs, svsum)
return sh, sv
ghs, gvs = Widget.get_grow(self) gh = (self.left.get_grow()[0] + self.right.get_grow()[0] + self.center.get_grow()[0]) * ghs gv = (self.top.get_grow()[1] + self.bottom.get_grow()[1] + self.center.get_grow()[1]) * gvs return gh, gv
(sh, sv), (oh, ov) = self.legalize(size, offset)
sl, sr, st, sb, sc = [x.get_min_size() for x in ( self.left, self.right, self.top, self.bottom, self.center)] gl, gr, gt, gb, gc = [x.get_grow() for x in ( self.left, self.right, self.top, self.bottom, self.center)]
ah = sh - (sl[0]+sr[0]+sc[0]) av = sv - (st[1]+sb[1]+sc[1])
if ah < 0.0: raise GmtPyError("Container not wide enough for contents " "(FrameLayout, available: %g cm, needed: %g cm)" % (sh/cm, (sl[0]+sr[0]+sc[0])/cm)) if av < 0.0: raise GmtPyError("Container not high enough for contents " "(FrameLayout, available: %g cm, needed: %g cm)" % (sv/cm, (st[1]+sb[1]+sc[1])/cm))
slh, srh, sch = distribute((sl[0], sr[0], sc[0]), (gl[0], gr[0], gc[0]), ah) stv, sbv, scv = distribute((st[1], sb[1], sc[1]), (gt[1], gb[1], gc[1]), av)
if self.center.aspect is not None: ahm = sh - (sl[0]+sr[0] + scv/self.center.aspect) avm = sv - (st[1]+sb[1] + sch*self.center.aspect) if 0.0 < ahm < ah: slh, srh, sch = distribute( (sl[0], sr[0], scv/self.center.aspect), (gl[0], gr[0], 0.0), ahm)
elif 0.0 < avm < av: stv, sbv, scv = distribute((st[1], sb[1], sch*self.center.aspect), (gt[1], gb[1], 0.0), avm)
ah = sh - (slh+srh+sch) av = sv - (stv+sbv+scv)
oh += ah/2. ov += av/2. sh -= ah sv -= av
self.left.set_size((slh, scv), (oh, ov+sbv)) self.right.set_size((srh, scv), (oh+slh+sch, ov+sbv)) self.top.set_size((sh, stv), (oh, ov+sbv+scv)) self.bottom.set_size((sh, sbv), (oh, ov)) self.center.set_size((sch, scv), (oh+slh, ov+sbv)) Widget.set_size(self, (sh, sv), (oh, ov))
'''Set one of the sub-widgets.
``which`` should be one of ``'left'``, ``'right'``, ``'top'``, ``'bottom'`` or ``'center'``. '''
if widget is None: widget = Widget()
if which in ('left', 'right', 'top', 'bottom', 'center'): self.__dict__[which] = widget else: raise GmtPyError('No such sub-widget: %s' % which)
widget.set_parent(self)
'''Get one of the sub-widgets.
``which`` should be one of ``'left'``, ``'right'``, ``'top'``, ``'bottom'`` or ``'center'``. '''
if which in ('left', 'right', 'top', 'bottom', 'center'): return self.__dict__[which] else: raise GmtPyError('No such sub-widget: %s' % which)
return [self.left, self.right, self.top, self.bottom, self.center]
'''A layout manager which arranges its sub-widgets in a grid.
The grid spacing is flexible and based on the sizing policies of the contained sub-widgets. If an equidistant grid is needed, the sizing policies of the sub-widgets have to be set equally.
The height of each row and the width of each column is derived from the sizing policy of the largest sub-widget in the row or column in question. The algorithm is not very sophisticated, so conflicting sizing policies might not be resolved optimally. '''
'''Create new grid layout with ``nx`` columns and ``ny`` rows.'''
Widget.__init__(self, horizontal, vertical) self.grid = [] for iy in range(ny): row = [] for ix in range(nx): w = Widget(parent=self) row.append(w)
self.grid.append(row)
esh = num.array( [[w.get_min_size()[0] for w in row] for row in self.grid], dtype=num.float) esv = num.array( [[w.get_min_size()[1] for w in row] for row in self.grid], dtype=num.float) return esh, esv
egh = num.array( [[w.get_grow()[0] for w in row] for row in self.grid], dtype=num.float) egv = num.array( [[w.get_grow()[1] for w in row] for row in self.grid], dtype=num.float) return egh, egv
sh, sv = Widget.get_min_size(self) esh, esv = self.sub_min_sizes_as_array() if esh.size != 0: sh = max(sh, num.sum(esh.max(0))) if esv.size != 0: sv = max(sv, num.sum(esv.max(1))) return sh, sv
ghs, gvs = Widget.get_grow(self) egh, egv = self.sub_grows_as_array() if egh.size != 0: gh = num.sum(egh.max(0))*ghs else: gh = 1.0 if egv.size != 0: gv = num.sum(egv.max(1))*gvs else: gv = 1.0 return gh, gv
(sh, sv), (oh, ov) = self.legalize(size, offset) esh, esv = self.sub_min_sizes_as_array() egh, egv = self.sub_grows_as_array()
# available additional space empty = esh.size == 0
if not empty: ah = sh - num.sum(esh.max(0)) av = sv - num.sum(esv.max(1)) else: av = sv ah = sh
if ah < 0.0: raise GmtPyError("Container not wide enough for contents " "(GridLayout, available: %g cm, needed: %g cm)" % (sh/cm, (num.sum(esh.max(0)))/cm)) if av < 0.0: raise GmtPyError("Container not high enough for contents " "(GridLayout, available: %g cm, needed: %g cm)" % (sv/cm, (num.sum(esv.max(1)))/cm))
nx, ny = esh.shape
if not empty: # distribute additional space on rows and columns # according to grow weights and minimal sizes gsh = egh.sum(1)[:, num.newaxis].repeat(ny, axis=1) nesh = esh.copy() nesh += num.where(gsh > 0.0, ah*egh/gsh, 0.0)
nsh = num.maximum(nesh.max(0), esh.max(0))
gsv = egv.sum(0)[num.newaxis, :].repeat(nx, axis=0) nesv = esv.copy() nesv += num.where(gsv > 0.0, av*egv/gsv, 0.0) nsv = num.maximum(nesv.max(1), esv.max(1))
ah = sh - sum(nsh) av = sv - sum(nsv)
oh += ah/2. ov += av/2. sh -= ah sv -= av
# resize child widgets neov = ov + sum(nsv) for row, nesv in zip(self.grid, nsv): neov -= nesv neoh = oh for w, nesh in zip(row, nsh): w.set_size((nesh, nesv), (neoh, neov)) neoh += nesh
Widget.set_size(self, (sh, sv), (oh, ov))
'''Set one of the sub-widgets.
Sets the sub-widget in column ``ix`` and row ``iy``. The indices are counted from zero. '''
if widget is None: widget = Widget()
self.grid[iy][ix] = widget widget.set_parent(self)
'''Get one of the sub-widgets.
Gets the sub-widget from column ``ix`` and row ``iy``. The indices are counted from zero.'''
return self.grid[iy][ix]
children = [] for row in self.grid: children.extend(row)
return children
return get_gmt_installation(version)['version'][0] == '5'
gmt = GMT(version=gmtversion, eps_mode=True)
if gmt.is_gmt5(): gmt.psbasemap('-B+gblack', finish=True, *args, **kwargs) fn = gmt.tempfilename('test.eps') gmt.save(fn, crop_eps_mode=True) with open(fn, 'rb') as f: s = f.read()
l, b, r, t = get_bbox(s) else: gmt.psbasemap('-G0', finish=True, *args, **kwargs) l, b, r, t = gmt.bbox()
return (t-b)/(r-l)
text, font=0, font_size=12., angle=0, gmtversion='newest', **kwargs):
gmt = GMT(version=gmtversion) if gmt.is_gmt5(): row = [0, 0, text] farg = ['-F+f%gp,%s,%s+j%s' % (font_size, font, 'black', 'BL')] else: row = [0, 0, font_size, 0, font, 'BL', text] farg = []
gmt.pstext( in_rows=[row], finish=True, R=(0, 1, 0, 1), J='x10p', N=True, *farg, **kwargs)
fn = gmt.tempfilename() + '.ps' gmt.save(fn)
(_, stderr) = subprocess.Popen( ['gs', '-q', '-dNOPAUSE', '-dBATCH', '-r720', '-sDEVICE=bbox', fn], stderr=subprocess.PIPE).communicate()
dx, dy = None, None for line in stderr.splitlines(): if line.startswith(b'%%HiResBoundingBox:'): l, b, r, t = [float(x) for x in line.split()[-4:]] dx, dy = r-l, t-b break
return dx, dy
'''Utility class to turn tables into lines.'''
self.in_columns = in_columns self.in_rows = in_rows self.encoding = encoding
if self.in_columns is not None: for row in zip(*self.in_columns): yield (' '.join([text(x) for x in row])+'\n').encode( self.encoding)
if self.in_rows is not None: for row in self.in_rows: yield (' '.join([text(x) for x in row])+'\n').encode( self.encoding)
'''File-like object to buffer data.'''
self.chopsize = None self.liner = liner self.chop_iterator = None self.closed = False
buf = BytesIO() for line in self.liner: buf.write(line) buflen = buf.tell() if self.chopsize is not None and buflen >= self.chopsize: buf.seek(0) while buf.tell() <= buflen-self.chopsize: yield buf.read(self.chopsize)
newbuf = BytesIO() newbuf.write(buf.read()) buf.close() buf = newbuf
yield(buf.getvalue()) buf.close()
if self.closed: raise ValueError('Cannot read from closed LineStreamChopper.') if self.chop_iterator is None: self.chopsize = size self.chop_iterator = self._chopiter()
self.chopsize = size try: return next(self.chop_iterator) except StopIteration: return ''
self.chopsize = None self.chop_iterator = None self.closed = True
pass
0: 'Helvetica', 1: 'Helvetica-Bold', }
'''A thin wrapper to GMT command execution.
A dict ``config`` may be given to override some of the default GMT parameters. The ``version`` argument may be used to select a specific GMT version, which should be used with this GMT instance. The selected version of GMT has to be installed on the system, must be supported by gmtpy and gmtpy must know where to find it.
Each instance of this class is used for the task of producing one PS or PDF output file.
Output of a series of GMT commands is accumulated in memory and can then be saved as PS or PDF file using the :py:meth:`save` method.
GMT commands are accessed as method calls to instances of this class. See the :py:meth:`__getattr__` method for details on how the method's arguments are translated into options and arguments for the GMT command.
Associated with each instance of this class, a temporary directory is created, where temporary files may be created, and which is automatically deleted, when the object is destroyed. The :py:meth:`tempfilename` method may be used to get a random filename in the instance's temporary directory.
Any .gmtdefaults files are ignored. The GMT class uses a fixed set of defaults, which may be altered via an argument to the constructor. If possible, GMT is run in 'isolation mode', which was introduced with GMT version 4.2.2, by setting `GMT_TMPDIR` to the instance's temporary directory. With earlier versions of GMT, problems may arise with parallel execution of more than one GMT instance.
Each instance of the GMT class may pick a specific version of GMT which shall be used, so that, if multiple versions of GMT are installed on the system, different versions of GMT can be used simultaneously such that backward compatibility of the scripts can be maintained.
'''
self, config=None, kontinue=None, version='newest', config_papersize=None, eps_mode=False):
self.installation = get_gmt_installation(version) self.gmt_config = dict(self.installation['defaults']) self.eps_mode = eps_mode self._shutil = shutil
if config: self.gmt_config.update(config)
if config_papersize: if not isinstance(config_papersize, str): config_papersize = 'Custom_%ix%i' % ( int(config_papersize[0]), int(config_papersize[1]))
if self.is_gmt5(): self.gmt_config['PS_MEDIA'] = config_papersize else: self.gmt_config['PAPER_MEDIA'] = config_papersize
self.tempdir = tempfile.mkdtemp("", "gmtpy-") self.gmt_config_filename = pjoin(self.tempdir, 'gmt.conf') self.gen_gmt_config_file(self.gmt_config_filename, self.gmt_config)
if kontinue is not None: self.load_unfinished(kontinue) self.needstart = False else: self.output = BytesIO() self.needstart = True
self.finished = False
self.environ = os.environ.copy() self.environ['GMTHOME'] = self.installation.get('home', '') # GMT isolation mode: works only properly with GMT version >= 4.2.2 self.environ['GMT_TMPDIR'] = self.tempdir
self.layout = None self.command_log = [] self.keep_temp_dir = False
return self.installation['version'][0] == '5'
return self.installation['version']
return self.gmt_config[key]
if not string: return 0
unit = string[-1] if unit in _units: return float(string[:-1])/_units[unit] else: default_unit = measure_unit(self.gmt_config).lower()[0] return float(string)/_units[default_unit]
if self.is_gmt5(): return self.to_points(self.gmt_config['FONT_LABEL'].split(',')[0]) else: return self.to_points(self.gmt_config['LABEL_FONT_SIZE'])
if self.is_gmt5(): return font_tab_rev(self.gmt_config['FONT_LABEL'].split(',')[1]) else: return self.gmt_config['LABEL_FONT']
f = open(config_filename, 'wb') f.write( ('#\n# GMT %s Defaults file\n' % self.installation['version']).encode('ascii'))
for k, v in config.items(): f.write(('%s = %s\n' % (k, v)).encode('ascii')) f.close()
if not self.keep_temp_dir: self._shutil.rmtree(self.tempdir)
'''Execute arbitrary GMT command.
See docstring in __getattr__ for details. '''
in_stream = kwargs.pop('in_stream', None) in_filename = kwargs.pop('in_filename', None) in_string = kwargs.pop('in_string', None) in_columns = kwargs.pop('in_columns', None) in_rows = kwargs.pop('in_rows', None) out_stream = kwargs.pop('out_stream', None) out_filename = kwargs.pop('out_filename', None) out_discard = kwargs.pop('out_discard', None) finish = kwargs.pop('finish', False) suppressdefaults = kwargs.pop('suppress_defaults', False) config_override = kwargs.pop('config', None)
assert(not self.finished)
# check for mutual exclusiveness on input and output possibilities assert(1 >= len( [x for x in [ in_stream, in_filename, in_string, in_columns, in_rows] if x is not None])) assert(1 >= len([x for x in [out_stream, out_filename, out_discard] if x is not None]))
options = []
gmt_config = self.gmt_config if not self.is_gmt5(): gmt_config_filename = self.gmt_config_filename if config_override: gmt_config = self.gmt_config.copy() gmt_config.update(config_override) gmt_config_override_filename = pjoin( self.tempdir, 'gmtdefaults_override') self.gen_gmt_config_file( gmt_config_override_filename, gmt_config) gmt_config_filename = gmt_config_override_filename
else: # gmt5 needs override variables as --VAR=value if config_override: for k, v in config_override.items(): options.append('--%s=%s' % (k, v))
if out_discard: out_filename = '/dev/null'
out_mustclose = False if out_filename is not None: out_mustclose = True out_stream = open(out_filename, 'wb')
if in_filename is not None: in_stream = open(in_filename, 'rb')
if in_string is not None: in_stream = BytesIO(in_string)
encoding_gmt = gmt_config.get( 'PS_CHAR_ENCODING', gmt_config.get('CHAR_ENCODING', 'ISOLatin1+'))
encoding = encoding_gmt_to_python[encoding_gmt.lower()]
if in_columns is not None or in_rows is not None: in_stream = LineStreamChopper(TableLiner(in_columns=in_columns, in_rows=in_rows, encoding=encoding))
# convert option arguments to strings for k, v in kwargs.items(): if len(k) > 1: raise GmtPyError('Found illegal keyword argument "%s" ' 'while preparing options for command "%s"' % (k, command))
if type(v) is bool: if v: options.append('-%s' % k) elif type(v) is tuple or type(v) is list: options.append('-%s' % k + '/'.join([str(x) for x in v])) else: options.append('-%s%s' % (k, str(v)))
# if not redirecting to an external sink, handle -K -O if out_stream is None: if not finish: options.append('-K') else: self.finished = True
if not self.needstart: options.append('-O') else: self.needstart = False
out_stream = self.output
# run the command if self.is_gmt5(): args = [pjoin(self.installation['bin'], 'gmt'), command] else: args = [pjoin(self.installation['bin'], command)]
if not os.path.isfile(args[0]): raise OSError('No such file: %s' % args[0]) args.extend(options) args.extend(addargs) if not self.is_gmt5() and not suppressdefaults: # does not seem to work with GMT 5 (and should not be necessary args.append('+'+gmt_config_filename)
bs = 2048 p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=bs, env=self.environ) while True: cr, cw, cx = select([p.stdout], [p.stdin], []) if cr: out_stream.write(p.stdout.read(bs)) if cw: if in_stream is not None: data = in_stream.read(bs) if len(data) == 0: break p.stdin.write(data) else: break if not cr and not cw: break
p.stdin.close()
while True: data = p.stdout.read(bs) if len(data) == 0: break out_stream.write(data)
p.stdout.close()
retcode = p.wait()
if in_stream is not None: in_stream.close()
if out_mustclose: out_stream.close()
if retcode != 0: self.keep_temp_dir = True raise GMTError('Command %s returned an error. ' 'While executing command:\n%s' % (command, escape_shell_args(args)))
self.command_log.append(args)
'''Maps to call self._gmtcommand(command, \\*addargs, \\*\\*kwargs).
Execute arbitrary GMT command.
Run a GMT command and by default append its postscript output to the output file maintained by the GMT instance on which this method is called.
Except for a few keyword arguments listed below, any ``kwargs`` and ``addargs`` are converted into command line options and arguments and passed to the GMT command. Numbers in keyword arguments are converted into strings. E.g. ``S=10`` is translated into ``'-S10'``. Tuples of numbers or strings are converted into strings where the elements of the tuples are separated by slashes '/'. E.g. ``R=(10, 10, 20, 20)`` is translated into ``'-R10/10/20/20'``. Options with a boolean argument are only appended to the GMT command, if their values are True.
If no output redirection is in effect, the -K and -O options are handled by gmtpy and thus should not be specified. Use ``out_discard=True`` if you don't want -K or -O beeing added, but are not interested in the output.
The standard input of the GMT process is fed by data selected with one of the following ``in_*`` keyword arguments:
=============== ======================================================= ``in_stream`` Data is read from an open file like object. ``in_filename`` Data is read from the given file. ``in_string`` String content is dumped to the process. ``in_columns`` A 2D nested iterable whose elements can be accessed as ``in_columns[icolumn][irow]`` is converted into an ascii table, which is fed to the process. ``in_rows`` A 2D nested iterable whos elements can be accessed as ``in_rows[irow][icolumn]`` is converted into an ascii table, which is fed to the process. =============== =======================================================
The standard output of the GMT process may be redirected by one of the following options:
================= ===================================================== ``out_stream`` Output is fed to an open file like object. ``out_filename`` Output is dumped to the given file. ``out_discard`` If True, output is dumped to :file:`/dev/null`. ================= =====================================================
Additional keyword arguments:
===================== ================================================= ``config`` Dict with GMT defaults which override the currently active set of defaults exclusively during this call. ``finish`` If True, the postscript file, which is maintained by the GMT instance is finished, and no further plotting is allowed. ``suppress_defaults`` Suppress appending of the ``'+gmtdefaults'`` option to the command. ===================== =================================================
'''
def f(*args, **kwargs): return self._gmtcommand(command, *args, **kwargs) return f
'''Get filename for temporary file in the private temp directory.
If no ``name`` argument is given, a random name is picked. If ``name`` is given, returns a path ending in that ``name``.'''
if not name: name = ''.join( [random.choice('abcdefghijklmnopqrstuvwxyz') for i in range(10)])
fn = pjoin(self.tempdir, name) return fn
'''Create and open a file in the private temp directory.'''
fn = self.tempfilename(name) f = open(fn, 'wb') return f, fn
out = open(filename, 'wb') out.write(self.output.getvalue()) out.close()
self.output = BytesIO() self.finished = False inp = open(filename, 'rb') self.output.write(inp.read()) inp.close()
filename = self.tempfilename('breakpoint-%s' % ident) self.save_unfinished(filename)
filename = self.tempfilename('breakpoint-%s' % ident) self.load_unfinished(filename)
width=None, height=None, size=None, crop_eps_mode=False, psconvert=False):
''' Finish and save figure as PDF, PS or PPM file.
If filename ends with ``'.pdf'`` a PDF file is created by piping the GMT output through :program:`gmtpy-epstopdf`.
If filename ends with ``'.png'`` a PNG file is created by running :program:`gmtpy-epstopdf`, :program:`pdftocairo` and :program:`convert`. ``resolution`` specifies the resolution in DPI for raster file formats. Rasterization is done at a higher resolution if ``oversample`` is set to a value higher than one. The output image size can also be controlled by setting ``width``, ``height`` or ``size`` instead of ``resolution``. When ``size`` is given, the image is scaled so that ``max(width, height) == size``.
The bounding box is set according to the values given in ``bbox``. '''
if not self.finished: self.psxy(R=True, J=True, finish=True)
if filename: tempfn = pjoin(self.tempdir, 'incomplete') out = open(tempfn, 'wb') else: out = sys.stdout
if bbox and not self.is_gmt5(): out.write(replace_bbox(bbox, self.output.getvalue())) else: out.write(self.output.getvalue())
if filename: out.close()
if filename.endswith('.ps') or ( not self.is_gmt5() and filename.endswith('.eps')):
shutil.move(tempfn, filename) return
if self.is_gmt5(): if crop_eps_mode: addarg = ['-A0'] else: addarg = []
subprocess.call( [pjoin(self.installation['bin'], 'gmt'), 'psconvert', '-Te', '-F%s' % tempfn + '.eps', tempfn, ] + addarg)
if bbox: with open(tempfn + '.eps', 'rb') as fin: with open(tempfn + '-fixbb.eps', 'wb') as fout: replace_bbox(bbox, fin, fout)
shutil.move(tempfn + '-fixbb.eps', tempfn + '.eps')
else: shutil.move(tempfn, tempfn + '.eps')
if filename.endswith('.pdf'): if psconvert: gmt_bin = pjoin(self.installation['bin'], 'gmt') subprocess.call([gmt_bin, 'psconvert', tempfn, '-Tf', '-F' + filename]) else: subprocess.call(['gmtpy-epstopdf', '--res=%i' % resolution, '--outfile=' + filename, tempfn + '.eps']) else: subprocess.call([ 'gmtpy-epstopdf', '--res=%i' % (resolution * oversample), '--outfile=' + tempfn + '.pdf', tempfn + '.eps'])
convert_graph( tempfn + '.pdf', filename, resolution=resolution, oversample=oversample, size=size, width=width, height=height)
return get_bbox(self.output.getvalue())
'''Get the command log.'''
return self.command_log
def __str__(self): s = '' for com in self.command_log: s += com[0] + "\n " + "\n ".join(com[1:]) + "\n\n" return s
'''Try to get paper size of output postscript file in points.'''
pm = paper_media(self.gmt_config).lower() if pm.endswith('+') or pm.endswith('-'): pm = pm[:-1]
orient = page_orientation(self.gmt_config).lower()
if pm in all_paper_sizes():
if orient == 'portrait': return get_paper_size(pm) else: return get_paper_size(pm)[1], get_paper_size(pm)[0]
m = re.match(r'custom_([0-9.]+)([cimp]?)x([0-9.]+)([cimp]?)', pm) if m: w, uw, h, uh = m.groups() w, h = float(w), float(h) if uw: w *= _units[uw] if uh: h *= _units[uh] if orient == 'portrait': return w, h else: return h, w
return None, None
'''Get a default layout for the output page.
One of three different layouts is choosen, depending on the `PAPER_MEDIA` setting in the GMT configuration dict.
If `PAPER_MEDIA` ends with a ``'+'`` (EPS output is selected), a :py:class:`FrameLayout` is centered on the page, whose size is controlled by its center widget's size plus the margins of the :py:class:`FrameLayout`.
If `PAPER_MEDIA` indicates, that a custom page size is wanted by starting with ``'Custom_'``, a :py:class:`FrameLayout` is used to fill the complete page. The center widget's size is then controlled by the page's size minus the margins of the :py:class:`FrameLayout`.
In any other case, two FrameLayouts are nested, such that the outer layout attaches a 1 cm (printer) margin around the complete page, and the inner FrameLayout's center widget takes up as much space as possible under the constraint, that an aspect ratio of 1/golden_ratio is preserved.
In any case, a reference to the innermost :py:class:`FrameLayout` instance is returned. The top-level layout can be accessed by calling :py:meth:`Widget.get_parent` on the returned layout. '''
if self.layout is None: w, h = self.page_size_points()
if w is None or h is None: raise GmtPyError("Can't determine page size for layout")
pm = paper_media(self.gmt_config).lower()
if with_palette: palette_layout = GridLayout(3, 1) spacer = palette_layout.get_widget(1, 0) palette_widget = palette_layout.get_widget(2, 0) spacer.set_horizontal(0.5*cm) palette_widget.set_horizontal(0.5*cm)
if pm.endswith('+') or self.eps_mode: outer = CenterLayout() outer.set_policy((w, h), (0., 0.)) inner = FrameLayout() outer.set_widget(inner) if with_palette: inner.set_widget('center', palette_layout) widget = palette_layout else: widget = inner.get_widget('center') widget.set_policy((w/golden_ratio, 0.), (0., 0.), aspect=1./golden_ratio) mw = 3.0*cm inner.set_fixed_margins( mw, mw, mw/golden_ratio, mw/golden_ratio) self.layout = inner
elif pm.startswith('custom_'): layout = FrameLayout() layout.set_policy((w, h), (0., 0.)) mw = 3.0*cm layout.set_min_margins( mw, mw, mw/golden_ratio, mw/golden_ratio) if with_palette: layout.set_widget('center', palette_layout) self.layout = layout else: outer = FrameLayout() outer.set_policy((w, h), (0., 0.)) outer.set_fixed_margins(1.*cm, 1.*cm, 1.*cm, 1.*cm)
inner = FrameLayout() outer.set_widget('center', inner) mw = 3.0*cm inner.set_min_margins(mw, mw, mw/golden_ratio, mw/golden_ratio) if with_palette: inner.set_widget('center', palette_layout) widget = palette_layout else: widget = inner.get_widget('center')
widget.set_aspect(1./golden_ratio)
self.layout = inner
return self.layout
'''Use psxy to draw layout; for debugging'''
# corners = layout.get_corners(descend=True) rects = num.array(layout.get_sizes(), dtype=num.float) rects_wid = rects[:, 0, 0] rects_hei = rects[:, 0, 1] rects_center_x = rects[:, 1, 0] + rects_wid*0.5 rects_center_y = rects[:, 1, 1] + rects_hei*0.5 nrects = len(rects) prects = (rects_center_x, rects_center_y, num.arange(nrects), num.zeros(nrects), rects_hei, rects_wid)
# points = num.array(corners, dtype=num.float)
cptfile = self.tempfilename() + '.cpt' self.makecpt( C='ocean', T='%g/%g/%g' % (-nrects, nrects, 1), Z=True, out_filename=cptfile, suppress_defaults=True)
bb = layout.bbox() self.psxy( in_columns=prects, C=cptfile, W='1p', S='J', R=(bb[0], bb[2], bb[1], bb[3]), *layout.XYJ())
c = {} x = axname for x in ('', axname): for k in ('label', 'unit', 'scaled_unit', 'scaled_unit_factor', 'space', 'mode', 'approx_ticks', 'limits', 'masking', 'inc', 'snap'):
if x+k in conf: c[k] = conf[x+k]
return Ax(**c)
contour=False, method='surface', zscaler=None, **extra): self.data = data self.cpt = cpt self.tension = tension self.size = size self.contour = contour self.method = method self.zscaler = zscaler self.extra = extra
self, data, size=9, justify='MC', fontno=0, offset=(0, 0), color='black'):
self.data = data self.size = size self.justify = justify self.fontno = fontno self.offset = offset self.color = color
self.data = [] self.symbols = [] self.config = copy.deepcopy(simple_config) self.gmtconfig = gmtconfig self.density_plot_defs = [] self.text_defs = []
self.gmtversion = gmtversion
self.data_x = [] self.symbols_x = []
self.data_y = [] self.symbols_y = []
self.default_config = {} self.set_defaults(width=15.*cm, height=15.*cm / golden_ratio, margins=(2.*cm, 2.*cm, 2.*cm, 2.*cm), with_palette=False, palette_offset=0.5*cm, palette_width=None, palette_height=None, zlabeloffset=2*cm, draw_layout=False)
self.setup_defaults() self.fixate_widget_aspect = False
pass
self.default_config.update(kwargs)
self.data.append(data) self.symbols.append(symbol)
dpd = DensityPlotDef(data, **kwargs) self.density_plot_defs.append(dpd)
dpd = TextDef(data, **kwargs) self.text_defs.append(dpd)
self.data_x.append(data) self.symbols_x.append(symbol)
self.data_y.append(data) self.symbols_y.append(symbol)
self.config.update(kwargs)
w = conf.pop('width') h = conf.pop('height') margins = conf.pop('margins')
gmtconfig = {} if self.gmtconfig is not None: gmtconfig.update(self.gmtconfig)
gmt = GMT( version=self.gmtversion, config=gmtconfig, config_papersize='Custom_%ix%i' % (w, h))
layout = gmt.default_layout(with_palette=conf['with_palette']) layout.set_min_margins(*margins) if conf['with_palette']: widget = layout.get_widget().get_widget(0, 0) spacer = layout.get_widget().get_widget(1, 0) spacer.set_horizontal(conf['palette_offset']) palette_widget = layout.get_widget().get_widget(2, 0) if conf['palette_width'] is not None: palette_widget.set_horizontal(conf['palette_width']) if conf['palette_height'] is not None: palette_widget.set_vertical(conf['palette_height']) widget.set_vertical(h-margins[2]-margins[3]-0.03*cm) return gmt, layout, widget, palette_widget else: widget = layout.get_widget() return gmt, layout, widget, None
pass
ndims = 2 if self.density_plot_defs: ndims = 3
axes = [simpleconf_to_ax(conf, x) for x in 'xyz'[:ndims]]
data_all = [] data_all.extend(self.data) for dsd in self.density_plot_defs: if dsd.zscaler is None: data_all.append(dsd.data) else: data_all.append(dsd.data[:2]) data_chopped = [ds[:ndims] for ds in data_all]
scaler = ScaleGuru(data_chopped, axes=axes[:ndims])
self.setup_scaling_plus(scaler, axes[:ndims])
return scaler
pass
scaler_x = scaler.copy() scaler_x.data_ranges[1] = (0., 1.) scaler_x.axes[1].mode = 'off'
scaler_y = scaler.copy() scaler_y.data_ranges[0] = (0., 1.) scaler_y.axes[0].mode = 'off'
return scaler_x, scaler_y
R = scaler.R() # par = scaler.get_params() rxyj = R + widget.XYJ() innerticks = False for dpd in self.density_plot_defs:
fn_cpt = gmt.tempfilename() + '.cpt'
if dpd.zscaler is not None: s = dpd.zscaler else: s = scaler
gmt.makecpt(C=dpd.cpt, out_filename=fn_cpt, *s.T())
fn_grid = gmt.tempfilename()
fn_mean = gmt.tempfilename()
if dpd.method in ('surface', 'triangulate'): gmt.blockmean(in_columns=dpd.data, I='%i+/%i+' % dpd.size, # noqa out_filename=fn_mean, *R)
if dpd.method == 'surface': gmt.surface( in_filename=fn_mean, T=dpd.tension, G=fn_grid, I='%i+/%i+' % dpd.size, # noqa out_discard=True, *R)
if dpd.method == 'triangulate': gmt.triangulate( in_filename=fn_mean, G=fn_grid, I='%i+/%i+' % dpd.size, # noqa out_discard=True, V=True, *R)
if gmt.is_gmt5(): gmt.grdimage(fn_grid, C=fn_cpt, E='i', n='l', *rxyj)
else: gmt.grdimage(fn_grid, C=fn_cpt, E='i', S='l', *rxyj)
if dpd.contour: gmt.grdcontour(fn_grid, C=fn_cpt, W='0.5p,black', *rxyj) innerticks = '0.5p,black'
os.remove(fn_grid) os.remove(fn_mean)
if dpd.method == 'fillcontour': extra = dict(C=fn_cpt) extra.update(dpd.extra) gmt.pscontour(in_columns=dpd.data, I=True, *rxyj, **extra) # noqa
if dpd.method == 'contour': extra = dict(W='0.5p,black', C=fn_cpt) extra.update(dpd.extra) gmt.pscontour(in_columns=dpd.data, *rxyj, **extra)
return fn_cpt, innerticks
gmt.psbasemap(*(widget.JXY() + scaler.RB(ax_projection=True)))
rxyj = scaler.R() + widget.JXY() for dat, sym in zip(self.data, self.symbols): gmt.psxy(in_columns=dat, *(sym.split()+rxyj))
pass
pass
for dat, sym in zip(self.data_x, self.symbols_x): gmt.psxy(in_columns=dat, *(sym.split() + scaler_x.R() + widget.JXY()))
for dat, sym in zip(self.data_y, self.symbols_y): gmt.psxy(in_columns=dat, *(sym.split() + scaler_y.R() + widget.JXY()))
rxyj = scaler.R() + widget.JXY() for td in self.text_defs: x, y = td.data[0:2] text = td.data[-1] size = td.size angle = 0 fontno = td.fontno justify = td.justify color = td.color if gmt.is_gmt5(): gmt.pstext( in_rows=[(x, y, text)], F='+f%gp,%s,%s+a%g+j%s' % ( size, fontno, color, angle, justify), D='%gp/%gp' % td.offset, *rxyj) else: gmt.pstext( in_rows=[(x, y, size, angle, fontno, justify, text)], D='%gp/%gp' % td.offset, *rxyj)
conf = dict(self.default_config) conf.update(self.config)
gmt, layout, widget, palette_widget = self.setup_base(conf) scaler = self.setup_scaling(conf) scaler_x, scaler_y = self.setup_scaling_extra(scaler, conf)
self.setup_projection(widget, scaler, conf) if self.fixate_widget_aspect: aspect = aspect_for_projection( gmt.installation['version'], *(widget.J() + scaler.R()))
widget.set_aspect(aspect)
if conf['draw_layout']: gmt.draw_layout(layout) cptfile = None if self.density_plot_defs: cptfile, innerticks = self.draw_density(gmt, widget, scaler) self.pre_draw(gmt, widget, scaler) self.draw(gmt, widget, scaler) self.post_draw(gmt, widget, scaler) self.draw_extra(gmt, widget, scaler_x, scaler_y) self.draw_text(gmt, widget, scaler) self.draw_basemap(gmt, widget, scaler)
if palette_widget and cptfile: nice_palette(gmt, palette_widget, scaler, cptfile, innerticks=innerticks, zlabeloffset=conf['zlabeloffset'])
gmt.save(filename, resolution=resolution)
self.set_defaults(xmode='min-max')
widget['J'] = '-JX%(width)gpl/%(height)gp' scaler['B'] = '-B2:%(xlabel)s:/%(yinc)g:%(ylabel)s:WSen'
self.set_defaults(ymode='min-max')
widget['J'] = '-JX%(width)gp/%(height)gpl' scaler['B'] = '-B%(xinc)g:%(xlabel)s:/2:%(ylabel)s:WSen'
self.set_defaults(mode='min-max')
widget['J'] = '-JX%(width)gpl/%(height)gpl' scaler['B'] = '-B2:%(xlabel)s:/2:%(ylabel)s:WSen'
Simple.__init__(self, *args, **kwargs) self.fixate_widget_aspect = True
self.set_defaults( height=15.*cm, width=15.*cm, xmode='off', xlimits=(0., 360.), xinc=45.)
widget['J'] = '-JPa%(width)gp'
scaler['B'] = '-B%(xinc)g:%(xlabel)s:/%(yinc)g:%(ylabel)s:N'
Simple.__init__(self, *args, **kwargs) self.fixate_widget_aspect = True
self.set_defaults(xmode='min-max', ymode='min-max')
par = scaler.get_params() lon0 = (par['xmin'] + par['xmax'])/2. lat0 = (par['ymin'] + par['ymax'])/2. sll = '%g/%g' % (lon0, lat0) widget['J'] = '-JM' + sll + '/%(width)gp' scaler['B'] = \ '-B%(xinc)gg%(xinc)g:%(xlabel)s:/%(yinc)gg%(yinc)g:%(ylabel)s:WSen'
innerticks=True):
par = scaleguru.get_params() par_ax = scaleguru.get_params(ax_projection=True) nz_palette = int(widget.height()/inch * 300) px = num.zeros(nz_palette*2) px[1::2] += 1 pz = num.linspace(par['zmin'], par['zmax'], nz_palette).repeat(2) pdz = pz[2]-pz[0] palgrdfile = gmt.tempfilename() pal_r = (0, 1, par['zmin'], par['zmax']) pal_ax_r = (0, 1, par_ax['zmin'], par_ax['zmax']) gmt.xyz2grd( G=palgrdfile, R=pal_r, I=(1, pdz), in_columns=(px, pz, pz), # noqa out_discard=True)
gmt.grdimage(palgrdfile, R=pal_r, C=cptfile, *widget.JXY()) if isinstance(innerticks, str): tickpen = innerticks gmt.grdcontour(palgrdfile, W=tickpen, R=pal_r, C=cptfile, *widget.JXY())
negpalwid = '%gp' % -widget.width() if not isinstance(innerticks, str) and innerticks: ticklen = negpalwid else: ticklen = '0p'
TICK_LENGTH_PARAM = 'MAP_TICK_LENGTH' if gmt.is_gmt5() else 'TICK_LENGTH' gmt.psbasemap( R=pal_ax_r, B='4::/%(zinc)g::nsw' % par_ax, config={TICK_LENGTH_PARAM: ticklen}, *widget.JXY())
if innerticks: gmt.psbasemap( R=pal_ax_r, B='4::/%(zinc)g::E' % par_ax, config={TICK_LENGTH_PARAM: '0p'}, *widget.JXY()) else: gmt.psbasemap(R=pal_ax_r, B='4::/%(zinc)g::E' % par_ax, *widget.JXY())
if par_ax['zlabel']: label_font = gmt.label_font() label_font_size = gmt.label_font_size() label_offset = zlabeloffset gmt.pstext( R=(0, 1, 0, 2), D="%gp/0p" % label_offset, N=True, in_rows=[(1, 1, label_font_size, -90, label_font, 'CB', par_ax['zlabel'])], *widget.JXY()) |