forked from pyrocko/pyrocko
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4400 lines
133 KiB
Python
4400 lines
133 KiB
Python
# http://pyrocko.org - GPLv3
|
|
#
|
|
# The Pyrocko Developers, 21st Century
|
|
# ---|P------/S----------~Lg----------
|
|
'''
|
|
A Python interface to GMT.
|
|
'''
|
|
|
|
# This file is part of GmtPy (http://emolch.github.io/gmtpy/)
|
|
# See there for copying and licensing information.
|
|
|
|
from __future__ import print_function, absolute_import
|
|
import subprocess
|
|
try:
|
|
from StringIO import StringIO as BytesIO
|
|
except ImportError:
|
|
from io import BytesIO
|
|
import re
|
|
import os
|
|
import sys
|
|
import shutil
|
|
from os.path import join as pjoin
|
|
import tempfile
|
|
import random
|
|
import logging
|
|
import math
|
|
import numpy as num
|
|
import copy
|
|
from select import select
|
|
from scipy.io import netcdf
|
|
|
|
from pyrocko import ExternalProgramMissing
|
|
|
|
try:
|
|
newstr = unicode
|
|
except NameError:
|
|
newstr = str
|
|
|
|
find_bb = re.compile(br'%%BoundingBox:((\s+[-0-9]+){4})')
|
|
find_hiresbb = re.compile(br'%%HiResBoundingBox:((\s+[-0-9.]+){4})')
|
|
|
|
|
|
encoding_gmt_to_python = {
|
|
'isolatin1+': 'iso-8859-1',
|
|
'standard+': 'ascii',
|
|
'isolatin1': 'iso-8859-1',
|
|
'standard': 'ascii'}
|
|
|
|
for i in range(1, 11):
|
|
encoding_gmt_to_python['iso-8859-%i' % i] = 'iso-8859-%i' % i
|
|
|
|
|
|
def have_gmt():
|
|
try:
|
|
get_gmt_installation('newest')
|
|
return True
|
|
|
|
except GMTInstallationProblem:
|
|
return False
|
|
|
|
|
|
def check_have_gmt():
|
|
if not have_gmt():
|
|
raise ExternalProgramMissing('GMT is not installed or cannot be found')
|
|
|
|
|
|
def have_pixmaptools():
|
|
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
|
|
|
|
|
|
class GmtPyError(Exception):
|
|
pass
|
|
|
|
|
|
class GMTError(GmtPyError):
|
|
pass
|
|
|
|
|
|
class GMTInstallationProblem(GmtPyError):
|
|
pass
|
|
|
|
|
|
def convert_graph(in_filename, out_filename, resolution=75., oversample=2.,
|
|
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)
|
|
|
|
|
|
def get_bbox(s):
|
|
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 replace_bbox(bbox, *args):
|
|
|
|
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)
|
|
|
|
|
|
def escape_shell_arg(s):
|
|
'''
|
|
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
|
|
|
|
|
|
def escape_shell_args(args):
|
|
'''
|
|
This function should be used for debugging output only - it could be
|
|
insecure.
|
|
'''
|
|
|
|
return ' '.join([escape_shell_arg(x) for x in args])
|
|
|
|
|
|
golden_ratio = 1.61803
|
|
|
|
# units in points
|
|
_units = {
|
|
'i': 72.,
|
|
'c': 72./2.54,
|
|
'm': 72.*100./2.54,
|
|
'p': 1.}
|
|
|
|
inch = _units['i']
|
|
cm = _units['c']
|
|
|
|
# some awsome colors
|
|
tango_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)
|
|
}
|
|
|
|
graph_colors = [tango_colors[_x] for _x in (
|
|
'scarletred2', 'skyblue3', 'chameleon3', 'orange2', 'plum2', 'chocolate2',
|
|
'butter2')]
|
|
|
|
|
|
def color(x=None):
|
|
'''
|
|
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
|
|
|
|
|
|
def color_tup(x=None):
|
|
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
|
|
|
|
|
|
_gmt_installations = {}
|
|
|
|
# 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
|
|
|
|
|
|
def key_version(a):
|
|
a = a.split('_')[0] # get rid of revision id
|
|
return [int(x) for x in a.split('.')]
|
|
|
|
|
|
def newest_installed_gmt_version():
|
|
return sorted(_gmt_installations.keys(), key=key_version)[-1]
|
|
|
|
|
|
def all_installed_gmt_versions():
|
|
return sorted(_gmt_installations.keys(), key=key_version)
|
|
|
|
|
|
# To have consistent defaults, they are hardcoded here and should not be
|
|
# changed.
|
|
|
|
_gmt_defaults_by_version = {}
|
|
_gmt_defaults_by_version['4.2.1'] = r'''
|
|
#
|
|
# 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_defaults_by_version['4.3.0'] = r'''
|
|
#
|
|
# 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_defaults_by_version['4.3.1'] = r'''
|
|
#
|
|
# 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_defaults_by_version['4.4.0'] = r'''
|
|
#
|
|
# 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_defaults_by_version['4.5.2'] = r'''
|
|
#
|
|
# 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_defaults_by_version['4.5.3'] = r'''
|
|
#
|
|
# 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_defaults_by_version['5.1.2'] = r'''
|
|
#
|
|
# 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
|
|
'''
|
|
|
|
|
|
def get_gmt_version(gmtdefaultsbinary, gmthomedir=None):
|
|
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'))
|
|
|
|
|
|
def detect_gmt_installations():
|
|
|
|
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 get 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
|
|
|
|
|
|
def appropriate_defaults_version(version):
|
|
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]
|
|
|
|
|
|
def gmt_default_config(version):
|
|
'''
|
|
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
|
|
|
|
|
|
def diff_defaults(v1, v2):
|
|
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')
|
|
|
|
|
|
def check_gmt_installation(installation):
|
|
|
|
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] == '6':
|
|
raise GMTInstallationProblem(
|
|
'pyrocko.gmtpy does not support GMT 6')
|
|
|
|
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))
|
|
|
|
|
|
def get_gmt_installation(version):
|
|
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
|
|
|
|
|
|
def setup_gmt_installations():
|
|
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
|
|
|
|
|
|
setup_gmt_installations.have_done = False
|
|
|
|
_paper_sizes_a = '''A0 2380 3368
|
|
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'''
|
|
|
|
|
|
_paper_sizes = {}
|
|
|
|
|
|
def setup_paper_sizes():
|
|
if not _paper_sizes:
|
|
for line in _paper_sizes_a.splitlines():
|
|
k, w, h = line.split()
|
|
_paper_sizes[k.lower()] = float(w), float(h)
|
|
|
|
|
|
def get_paper_size(k):
|
|
setup_paper_sizes()
|
|
return _paper_sizes[k.lower().rstrip('+')]
|
|
|
|
|
|
def all_paper_sizes():
|
|
setup_paper_sizes()
|
|
return _paper_sizes
|
|
|
|
|
|
def measure_unit(gmt_config):
|
|
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')
|
|
|
|
|
|
def paper_media(gmt_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')
|
|
|
|
|
|
def page_orientation(gmt_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')
|
|
|
|
|
|
def make_bbox(width, height, gmt_config, margins=(0.8, 0.8, 0.8, 0.8)):
|
|
|
|
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)
|
|
|
|
|
|
def gmtdefaults_as_text(version='newest'):
|
|
|
|
'''
|
|
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]
|
|
|
|
|
|
def savegrd(x, y, z, filename, title=None, naming='xy'):
|
|
'''
|
|
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()
|
|
|
|
|
|
def to_array(var):
|
|
arr = var[:].copy()
|
|
if hasattr(var, 'scale_factor'):
|
|
arr *= var.scale_factor
|
|
|
|
if hasattr(var, 'add_offset'):
|
|
arr += var.add_offset
|
|
|
|
return arr
|
|
|
|
|
|
def loadgrd(filename):
|
|
'''
|
|
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
|
|
|
|
|
|
def centers_to_edges(asorted):
|
|
return (asorted[1:] + asorted[:-1])/2.
|
|
|
|
|
|
def nvals(asorted):
|
|
eps = (asorted[-1]-asorted[0])/asorted.size
|
|
return num.sum(asorted[1:] - asorted[:-1] >= eps) + 1
|
|
|
|
|
|
def guess_vals(asorted):
|
|
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])
|
|
|
|
|
|
def blockmean(asorted, b):
|
|
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]))
|
|
|
|
|
|
def griddata_regular(x, y, z, xvals, yvals):
|
|
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=float)
|
|
znew[:] = num.nan
|
|
znew[zindi] = z
|
|
return znew.reshape(ny, nx)
|
|
|
|
|
|
def guess_field_size(x_sorted, y_sorted, z=None, mode=None):
|
|
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
|
|
|
|
|
|
def griddata_auto(x, y, z, mode=None):
|
|
'''
|
|
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
|
|
|
|
|
|
def tabledata(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
|
|
|
|
|
|
def double1d(a):
|
|
a2 = num.empty(a.size*2-1)
|
|
a2[::2] = a
|
|
a2[1::2] = (a[:-1] + a[1:])/2.
|
|
return a2
|
|
|
|
|
|
def double2d(f):
|
|
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
|
|
|
|
|
|
def doublegrid(x, y, z):
|
|
x2 = double1d(x)
|
|
y2 = double1d(y)
|
|
z2 = double2d(z)
|
|
return x2, y2, z2
|
|
|
|
|
|
class Guru(object):
|
|
'''
|
|
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.
|
|
'''
|
|
|
|
def __init__(self):
|
|
self.templates = {}
|
|
|
|
def fill(self, templates, **kwargs):
|
|
params = self.get_params(**kwargs)
|
|
strings = [t % params for t in templates]
|
|
return strings
|
|
|
|
# hand through templates dict
|
|
def __getitem__(self, template_name):
|
|
return self.templates[template_name]
|
|
|
|
def __setitem__(self, template_name, template):
|
|
self.templates[template_name] = template
|
|
|
|
def __contains__(self, template_name):
|
|
return template_name in self.templates
|
|
|
|
def __iter__(self):
|
|
return iter(self.templates)
|
|
|
|
def __len__(self):
|
|
return len(self.templates)
|
|
|
|
def __delitem__(self, template_name):
|
|
del(self.templates[template_name])
|
|
|
|
def _simple_fill(self, template_names, **kwargs):
|
|
templates = [self.templates[n] for n in template_names]
|
|
return self.fill(templates, **kwargs)
|
|
|
|
def __getattr__(self, template_names):
|
|
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
|
|
|
|
|
|
def nice_value(x):
|
|
'''
|
|
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
|
|
|
|
|
|
class AutoScaler(object):
|
|
'''
|
|
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.
|
|
|
|
'''
|
|
|
|
def __init__(
|
|
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
|
|
|
|
def make_scale(self, data_range, override_mode=None):
|
|
|
|
'''
|
|
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
|
|
|
|
def make_exp(self, x):
|
|
'''
|
|
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
|
|
|
|
def guess_autoscale_mode(self, data_min, data_max):
|
|
'''
|
|
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
|
|
|
|
|
|
class Ax(AutoScaler):
|
|
'''
|
|
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.
|
|
|
|
'''
|
|
|
|
def __init__(self, label='', unit='', scaled_unit_factor=1.,
|
|
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
|
|
|
|
def label_str(self, exp, unit):
|
|
'''
|
|
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)
|
|
|
|
def make_params(self, data_range, ax_projection=False, override_mode=None,
|
|
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
|
|
|
|
|
|
class ScaleGuru(Guru):
|
|
|
|
'''
|
|
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.
|
|
|
|
'''
|
|
|
|
def __init__(self, data_tuples=None, axes=None, aspect=None,
|
|
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
|
|
|
|
def copy(self):
|
|
return ScaleGuru(copy_from=self)
|
|
|
|
def get_params(self, ax_projection=False):
|
|
|
|
'''
|
|
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
|
|
|
|
|
|
class GumSpring(object):
|
|
|
|
'''
|
|
Sizing policy implementing a minimal size, plus a desire to grow.
|
|
'''
|
|
|
|
def __init__(self, minimal=None, grow=None):
|
|
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
|
|
|
|
def get_minimal(self):
|
|
if self.minimal is not None:
|
|
return self.minimal
|
|
else:
|
|
return 0.0
|
|
|
|
def get_grow(self):
|
|
return self.grow
|
|
|
|
def set_value(self, value):
|
|
self.value = value
|
|
|
|
def get_value(self):
|
|
return self.value
|
|
|
|
|
|
def distribute(sizes, grows, space):
|
|
sizes = list(sizes)
|
|
gsum = sum(grows)
|
|
if gsum > 0.0:
|
|
for i in range(len(sizes)):
|
|
sizes[i] += space*grows[i]/gsum
|
|
return sizes
|
|
|
|
|
|
class Widget(Guru):
|
|
|
|
'''
|
|
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.
|
|
'''
|
|
|
|
def __init__(self, horizontal=None, vertical=None, parent=None):
|
|
|
|
'''
|
|
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
|
|
|
|
def set_parent(self, parent):
|
|
|
|
'''
|
|
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()
|
|
|
|
def get_parent(self):
|
|
|
|
'''
|
|
Get the widgets parent widget.
|
|
'''
|
|
|
|
return self.parent
|
|
|
|
def get_root(self):
|
|
|
|
'''
|
|
Get the root widget in the layout hierarchy.
|
|
'''
|
|
|
|
if self.parent is not None:
|
|
return self.get_parent()
|
|
else:
|
|
return self
|
|
|
|
def set_horizontal(self, minimal=None, grow=None):
|
|
|
|
'''
|
|
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()
|
|
|
|
def get_horizontal(self):
|
|
return self.horizontal.get_minimal(), self.horizontal.get_grow()
|
|
|
|
def set_vertical(self, minimal=None, grow=None):
|
|
|
|
'''
|
|
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()
|
|
|
|
def get_vertical(self):
|
|
return self.vertical.get_minimal(), self.vertical.get_grow()
|
|
|
|
def set_aspect(self, aspect=None):
|
|
|
|
'''
|
|
Set aspect constraint on the widget.
|
|
|
|
The aspect is given as height divided by width.
|
|
'''
|
|
|
|
self.aspect = aspect
|
|
self.dirtyfy()
|
|
|
|
def set_policy(self, minimal=(None, None), grow=(None, None), aspect=None):
|
|
|
|
'''
|
|
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)
|
|
|
|
def get_policy(self):
|
|
mh, gh = self.get_horizontal()
|
|
mv, gv = self.get_vertical()
|
|
return (mh, mv), (gh, gv), self.aspect
|
|
|
|
def legalize(self, size, offset):
|
|
|
|
'''
|
|
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)
|
|
|
|
def get_min_size(self):
|
|
|
|
'''
|
|
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
|
|
|
|
def get_grow(self):
|
|
|
|
'''
|
|
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()
|
|
|
|
def set_size(self, size, offset):
|
|
|
|
'''
|
|
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 policies_debug_str(self):
|
|
|
|
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
|
|
|
|
def get_corners(self, descend=False):
|
|
|
|
'''
|
|
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
|
|
|
|
def get_sizes(self):
|
|
|
|
'''
|
|
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
|
|
|
|
def do_layout(self):
|
|
|
|
'''
|
|
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.))
|
|
|
|
def get_children(self):
|
|
|
|
'''
|
|
Get sub-widgets contained in this widget.
|
|
|
|
Returns a list of widgets.
|
|
'''
|
|
|
|
return []
|
|
|
|
def get_size(self):
|
|
|
|
'''
|
|
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
|
|
|
|
def get_params(self):
|
|
|
|
'''
|
|
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'])
|
|
|
|
def width(self):
|
|
|
|
'''
|
|
Get current width of the widget.
|
|
|
|
Triggers layouting and returns width.
|
|
'''
|
|
|
|
self.do_layout()
|
|
return self.horizontal.get_value()
|
|
|
|
def height(self):
|
|
|
|
'''
|
|
Get current height of the widget.
|
|
|
|
Triggers layouting and return height.
|
|
'''
|
|
|
|
self.do_layout()
|
|
return self.vertical.get_value()
|
|
|
|
def bbox(self):
|
|
|
|
'''
|
|
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())
|
|
|
|
def dirtyfy(self):
|
|
|
|
'''
|
|
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
|
|
|
|
|
|
class CenterLayout(Widget):
|
|
|
|
'''
|
|
A layout manager which centers its single child widget.
|
|
|
|
The child widget may be oversized.
|
|
'''
|
|
|
|
def __init__(self, horizontal=None, vertical=None):
|
|
Widget.__init__(self, horizontal, vertical)
|
|
self.content = Widget(horizontal=GumSpring(grow=1.),
|
|
vertical=GumSpring(grow=1.), parent=self)
|
|
|
|
def get_min_size(self):
|
|
shs, svs = Widget.get_min_size(self)
|
|
sh, sv = self.content.get_min_size()
|
|
return max(shs, sh), max(svs, sv)
|
|
|
|
def get_grow(self):
|
|
ghs, gvs = Widget.get_grow(self)
|
|
gh, gv = self.content.get_grow()
|
|
return gh*ghs, gv*gvs
|
|
|
|
def set_size(self, size, offset):
|
|
(sh, sv) |