A probabilistic earthquake source inversion framework. Designed and crafted in Mordor.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

153 lines
4.6 KiB

  1. import numpy as num
  2. import logging
  3. import math
  4. from pyrocko import plot
  5. from pyrocko.guts import Tuple, Float
  6. from matplotlib import pyplot as plt
  7. from matplotlib import lines
  8. from matplotlib.ticker import FuncFormatter
  9. from grond.plot.config import PlotConfig
  10. guts_prefix = 'grond'
  11. km = 1e3
  12. d2r = math.pi / 180.
  13. logger = logging.getLogger('targets.plot')
  14. class StationDistributionPlot(PlotConfig):
  15. ''' Plot showing all waveform fits for the ensemble of solutions'''
  16. name = 'seismic_stations_base'
  17. size_cm = Tuple.T(
  18. 2, Float.T(),
  19. default=(16., 13.),
  20. help='width and length of the figure in cm')
  21. font_size = Float.T(
  22. default=10,
  23. help='font size of all text, except station labels')
  24. font_size_labels = Float.T(
  25. default=6,
  26. help='font size of station labels')
  27. def plot_station_distribution(
  28. self, azimuths, distances, weights, labels=None,
  29. scatter_kwargs=dict(), annotate_kwargs=dict(), maxsize=10**2):
  30. invalid_color = plot.mpl_color('aluminium3')
  31. scatter_default = {
  32. 'alpha': .5,
  33. 'zorder': 10,
  34. 'c': plot.mpl_color('skyblue2'),
  35. }
  36. annotate_default = {
  37. 'alpha': .8,
  38. 'color': 'k',
  39. 'fontsize': self.font_size_labels,
  40. 'ha': 'right',
  41. 'va': 'top',
  42. 'xytext': (-5, -5),
  43. 'textcoords': 'offset points'
  44. }
  45. scatter_default.update(scatter_kwargs)
  46. annotate_default.update(annotate_kwargs)
  47. fig = plt.figure(figsize=self.size_inch)
  48. plot.mpl_margins(
  49. fig, nw=1, nh=1, left=3., right=10., top=3., bottom=3.,
  50. units=self.font_size)
  51. ax = fig.add_subplot(111, projection='polar')
  52. valid = num.isfinite(weights)
  53. valid[valid] = num.logical_and(valid[valid], weights[valid] > 0.0)
  54. weights = weights.copy()
  55. if num.sum(valid) == 0:
  56. weights[:] = 1.0
  57. weights_ref = 1.0
  58. else:
  59. weights[~valid] = weights[valid].min()
  60. weights_ref = plot.nice_value(weights[valid].max())
  61. if weights_ref == 0.:
  62. weights_ref = 1.0
  63. colors = [scatter_default['c'] if s else invalid_color
  64. for s in valid]
  65. scatter_default.pop('c')
  66. weights_scaled = (weights / weights_ref) * maxsize
  67. stations = ax.scatter(
  68. azimuths*d2r, distances, s=weights_scaled, c=colors,
  69. **scatter_default)
  70. if len(labels) < 30: # TODO: remove after impl. of collision detection
  71. if labels is not None:
  72. for ilbl, label in enumerate(labels):
  73. ax.annotate(
  74. label, (azimuths[ilbl]*d2r, distances[ilbl]),
  75. **annotate_default)
  76. ax.set_theta_zero_location('N')
  77. ax.set_theta_direction(-1)
  78. ax.tick_params('y', labelsize=self.font_size, labelcolor='gray')
  79. ax.grid(alpha=.3)
  80. ax.set_ylim(0, distances.max()*1.1)
  81. ax.yaxis.set_major_locator(plt.MaxNLocator(4))
  82. ax.yaxis.set_major_formatter(
  83. FuncFormatter(lambda x, pos: '%d km' % (x/km)))
  84. # Legend
  85. entries = 4
  86. valid_marker = num.argmax(valid)
  87. ecl = stations.get_edgecolor()
  88. fc = tuple(stations.get_facecolor()[valid_marker])
  89. ec = tuple(ecl[min(valid_marker, len(ecl)-1)])
  90. def get_min_precision(values):
  91. sig_prec = num.floor(
  92. num.isfinite(num.log10(values[values > 0])))
  93. if sig_prec.size == 0:
  94. return 1
  95. return int(abs(sig_prec.min())) + 1
  96. legend_artists = [
  97. lines.Line2D(
  98. [0], [0], ls='none',
  99. marker='o', ms=num.sqrt(rad), mfc=fc, mec=ec)
  100. for rad in num.linspace(maxsize, .1*maxsize, entries)
  101. ]
  102. sig_prec = get_min_precision(weights)
  103. legend_annot = [
  104. '{value:.{prec}f}'.format(value=val, prec=sig_prec)
  105. for val in num.linspace(weights_ref, .1*weights_ref, entries)
  106. ]
  107. if not num.all(valid):
  108. legend_artists.append(
  109. lines.Line2D(
  110. [0], [0], ls='none',
  111. marker='o', ms=num.sqrt(maxsize),
  112. mfc=invalid_color, mec=invalid_color))
  113. legend_annot.append('Excluded')
  114. legend = fig.legend(
  115. legend_artists, legend_annot,
  116. fontsize=self.font_size, loc=4,
  117. markerscale=1, numpoints=1,
  118. frameon=False)
  119. return fig, ax, legend