├── mopad ├── setup.py ├── README.md ├── LICENSE.txt └── mopad.py /mopad: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | python -m mopad "$@" 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name = 'mopad', 5 | version = '0.9b', 6 | description = 'Moment tensor Plotting and Decomposition tool', 7 | py_modules = ['mopad'], 8 | scripts = ['mopad'], 9 | author = 'Lars Krieger', 10 | author_email = 'lars.krieger@zmaw.de', 11 | url = 'http://geophysics.github.com/MoPaD/', 12 | 13 | ) 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MoPaD 2 | ===== 3 | 4 | the MoPaD repository 5 | 6 | Put to GITHUB in version 09b 7 | 8 | Python source: 9 | 10 | mopad.py 11 | 12 | 13 | License: 14 | 15 | MoPaD is an open source program. 16 | It has been developed under the LGPL license. 17 | The license agreement should always be kept together with the code. 18 | (License.txt) 19 | 20 | Copyright by Lars Krieger and Sebastian Heimann 2010. 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /mopad.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from cStringIO import StringIO 4 | import optparse 5 | import math 6 | import numpy as np 7 | import os 8 | import os.path 9 | import sys 10 | import re 11 | 12 | MOPAD_VERSION = 1.0 13 | 14 | 15 | # constants: 16 | dynecm = 1e-7 17 | pi = np.pi 18 | 19 | epsilon = 1e-13 20 | 21 | rad2deg = 180. / pi 22 | 23 | 24 | def wrap(text, line_length=80): 25 | '''Paragraph and list-aware wrapping of text.''' 26 | 27 | text = text.strip('\n') 28 | at_lineend = re.compile(r' *\n') 29 | at_para = re.compile(r'((^|(\n\s*)?\n)(\s+[*] )|\n\s*\n)') 30 | 31 | paragraphs = at_para.split(text)[::5] 32 | listindents = at_para.split(text)[4::5] 33 | newlist = at_para.split(text)[3::5] 34 | 35 | listindents[0:0] = [None] 36 | listindents.append(True) 37 | newlist.append(None) 38 | 39 | det_indent = re.compile(r'^ *') 40 | 41 | outlines = [] 42 | for ip, p in enumerate(paragraphs): 43 | if not p: 44 | continue 45 | 46 | if listindents[ip] is None: 47 | _indent = det_indent.findall(p)[0] 48 | findent = _indent 49 | else: 50 | findent = listindents[ip] 51 | _indent = ' ' * len(findent) 52 | 53 | ll = line_length - len(_indent) 54 | llf = ll 55 | 56 | oldlines = [s.strip() for s in at_lineend.split(p.rstrip())] 57 | p1 = ' '.join(oldlines) 58 | possible = re.compile(r'(^.{1,%i}|.{1,%i})( |$)' % (llf, ll)) 59 | for imatch, match in enumerate(possible.finditer(p1)): 60 | parout = match.group(1) 61 | if imatch == 0: 62 | outlines.append(findent + parout) 63 | else: 64 | outlines.append(_indent + parout) 65 | 66 | if ip != len(paragraphs) - 1 and ( 67 | listindents[ip] is None or 68 | newlist[ip] is not None or 69 | listindents[ip + 1] is None): 70 | 71 | outlines.append('') 72 | 73 | return outlines 74 | 75 | 76 | def basis_switcher(in_system, out_system): 77 | from_ned = { 78 | 'NED': np.matrix([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]], 79 | dtype=np.float), 80 | 'USE': np.matrix([[0., -1., 0.], [0., 0., 1.], [-1., 0., 0.]], 81 | dtype=np.float).I, 82 | 'XYZ': np.matrix([[0., 1., 0.], [1., 0., 0.], [0., 0., -1.]], 83 | dtype=np.float).I, 84 | 'NWU': np.matrix([[1., 0., 0.], [0., -1., 0.], [0., 0., -1.]], 85 | dtype=np.float).I} 86 | 87 | return from_ned[in_system].I * from_ned[out_system] 88 | 89 | 90 | def basis_transform_matrix(m, in_system, out_system): 91 | r = basis_switcher(in_system, out_system) 92 | return np.dot(r, np.dot(m, r.I)) 93 | 94 | 95 | def basis_transform_vector(v, in_system, out_system): 96 | r = basis_switcher(in_system, out_system) 97 | return np.dot(r, v) 98 | 99 | 100 | class MopadHelpFormatter(optparse.IndentedHelpFormatter): 101 | 102 | def format_option(self, option): 103 | '''From IndentedHelpFormatter but using a different wrap method.''' 104 | 105 | result = [] 106 | opts = self.option_strings[option] 107 | opt_width = self.help_position - self.current_indent - 2 108 | if len(opts) > opt_width: 109 | opts = "%*s%s\n" % (self.current_indent, "", opts) 110 | indent_first = self.help_position 111 | else: # start help on same line as opts 112 | opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts) 113 | indent_first = 0 114 | result.append(opts) 115 | if option.help: 116 | help_text = self.expand_default(option) 117 | help_lines = wrap(help_text, self.help_width) 118 | if len(help_lines) > 1: 119 | help_lines.append('') 120 | result.append("%*s%s\n" % (indent_first, "", help_lines[0])) 121 | result.extend(["%*s%s\n" % (self.help_position, "", line) 122 | for line in help_lines[1:]]) 123 | elif opts[-1] != "\n": 124 | result.append("\n") 125 | return "".join(result) 126 | 127 | def format_description(self, description): 128 | if not description: 129 | return "" 130 | desc_width = self.width - self.current_indent 131 | return '\n'.join(wrap(description, desc_width)) + "\n" 132 | 133 | 134 | class MTError(Exception): 135 | pass 136 | 137 | 138 | def euler_to_matrix(alpha, beta, gamma): 139 | '''Given the euler angles alpha,beta,gamma, create rotation matrix 140 | 141 | Given coordinate system (x,y,z) and rotated system (xs,ys,zs) 142 | the line of nodes is the intersection between the x-y and the xs-ys 143 | planes. 144 | alpha is the angle between the z-axis and the zs-axis. 145 | beta is the angle between the x-axis and the line of nodes. 146 | gamma is the angle between the line of nodes and the xs-axis. 147 | 148 | Usage for moment tensors: 149 | m_unrot = numpy.matrix([[0,0,-1],[0,0,0],[-1,0,0]]) 150 | rotmat = euler_to_matrix(dip,strike,-rake) 151 | m = rotmat.T * m_unrot * rotmat''' 152 | 153 | ca = math.cos(alpha) 154 | cb = math.cos(beta) 155 | cg = math.cos(gamma) 156 | sa = math.sin(alpha) 157 | sb = math.sin(beta) 158 | sg = math.sin(gamma) 159 | 160 | mat = np.matrix( 161 | [[cb * cg - ca * sb * sg, sb * cg + ca * cb * sg, sa * sg], 162 | [-cb * sg - ca * sb * cg, -sb * 163 | sg + ca * cb * cg, sa * cg], 164 | [sa * sb, -sa * cb, ca]], dtype=np.float) 165 | return mat 166 | 167 | 168 | class MomentTensor: 169 | 170 | _m_unrot = np.matrix( 171 | [[0., 0., -1.], [0., 0., 0.], [-1., 0., 0.]], dtype=np.float) 172 | 173 | def __init__(self, M=None, in_system='NED', out_system='NED', debug=0): 174 | 175 | """ 176 | Creates a moment tensor object on the basis of a provided mechanism M. 177 | 178 | If M is a non symmetric 3x3-matrix, the upper right triangle 179 | of the matrix is taken as reference. M is symmetrisised 180 | w.r.t. these entries. If M is provided as a 3-,4-,6-,7-tuple 181 | or array, it is converted into a matrix internally according 182 | to standard conventions (Aki & Richards). 183 | 184 | 'system' may be chosen as 'NED','USE','NWU', or 'XYZ'. 185 | 186 | 'debug' enables output on the shell at the intermediate steps. 187 | """ 188 | 189 | self._original_M = M[:] 190 | 191 | self._input_basis = in_system.upper() 192 | self._output_basis = out_system.upper() 193 | 194 | # bring M to symmetric matrix form 195 | self._M = self._setup_M(M, self._input_basis) 196 | 197 | # decomposition: 198 | self._decomposition_key = 1 199 | 200 | # eigenvector / principal-axes system: 201 | self._eigenvalues = None 202 | self._eigenvectors = None 203 | self._null_axis = None 204 | self._t_axis = None 205 | self._p_axis = None 206 | self._rotation_matrix = None 207 | 208 | # optional - maybe set afterwards by external application - for later 209 | # plotting: 210 | self._best_faultplane = None 211 | self._auxiliary_plane = None 212 | 213 | # initialise decomposition components 214 | self._DC = None 215 | self._DC_percentage = None 216 | self._DC2 = None 217 | self._DC2_percentage = None 218 | self._DC3 = None 219 | self._DC3_percentage = None 220 | 221 | self._iso = None 222 | self._iso_percentage = None 223 | self._devi = None 224 | self._devi_percentage = None 225 | self._CLVD = None 226 | self._CLVD_percentage = None 227 | 228 | self._isotropic = None 229 | self._deviatoric = None 230 | self._seismic_moment = None 231 | self._moment_magnitude = None 232 | 233 | self._decomp_attrib_map_keys = ('in', 'out', 'type', 234 | 'full', 235 | 'iso', 'iso_perc', 236 | 'dev', 'devi', 'devi_perc', 237 | 'dc', 'dc_perc', 238 | 'dc2', 'dc2_perc', 239 | 'dc3', 'dc3_perc', 240 | 'clvd', 'clvd_perc', 241 | 'mom', 'mag', 242 | 'eigvals', 'eigvecs', 243 | 't', 'n', 'p') 244 | 245 | self._decomp_attrib_map = dict(zip(self._decomp_attrib_map_keys, 246 | ('input_system', 'output_system', 247 | 'decomp_type', 'M', 248 | 'iso', 'iso_percentage', 249 | 'devi', 'devi', 'devi_percentage', 250 | 'DC', 'DC_percentage', 251 | 'DC2', 'DC2_percentage', 252 | 'DC3', 'DC3_percentage', 253 | 'CLVD', 'CLVD_percentage', 254 | 'moment', 'mag', 255 | 'eigvals', 'eigvecs', 256 | 't_axis', 'null_axis', 'p_axis') 257 | )) 258 | 259 | # carry out the MT decomposition - results are in basis NED 260 | self._decompose_M() 261 | 262 | # set the appropriate principal axis system: 263 | self._M_to_principal_axis_system() 264 | 265 | def _setup_M(self, mech, input_basis): 266 | """ 267 | Brings the provided mechanism into symmetric 3x3 matrix form. 268 | 269 | The source mechanism may be provided in different forms: 270 | 271 | * as 3x3 matrix - symmetry is checked - one basis system has to be 272 | chosen, or NED as default is taken 273 | * as 3-element tuple or array - interpreted as strike, dip, slip-rake 274 | angles in degree 275 | * as 4-element tuple or array - interpreted as strike, dip, slip-rake 276 | angles in degree + seismic scalar moment in Nm 277 | * as 6-element tuple or array - interpreted as the 6 independent 278 | entries of the moment tensor 279 | * as 7-element tuple or array - interpreted as the 6 independent 280 | entries of the moment tensor + seismic scalar moment in Nm 281 | * as 9-element tuple or array - interpreted as the 9 entries of the 282 | moment tensor - checked for symmetry 283 | * as a nesting of one of the upper types (e.g. a list of n-tuples); 284 | first element of outer nesting is taken 285 | """ 286 | # set source mechanism to matrix form 287 | 288 | if mech is None: 289 | raise MTError('Please provide a mechanism') 290 | 291 | # if some stupid nesting occurs 292 | if len(mech) == 1: 293 | mech = mech[0] 294 | 295 | # all 9 elements are given 296 | if np.prod(np.shape(mech)) == 9: 297 | if np.shape(mech)[0] == 3: 298 | # assure symmetry: 299 | mech[1, 0] = mech[0, 1] 300 | mech[2, 0] = mech[0, 2] 301 | mech[2, 1] = mech[1, 2] 302 | new_M = mech 303 | else: 304 | new_M = np.array(mech).reshape(3, 3).copy() 305 | new_M[1, 0] = new_M[0, 1] 306 | new_M[2, 0] = new_M[0, 2] 307 | new_M[2, 1] = new_M[1, 2] 308 | 309 | # mechanism given as 6- or 7-tuple, list or array 310 | elif len(mech) == 6 or len(mech) == 7: 311 | M = mech 312 | new_M = np.matrix( 313 | np.array([M[0], M[3], M[4], 314 | M[3], M[1], M[5], 315 | M[4], M[5], M[2]]).reshape(3, 3)) 316 | 317 | if len(mech) == 7: 318 | new_M = M[6] * new_M 319 | 320 | # if given as strike, dip, rake, conventions from Jost & Herrmann hold 321 | # - resulting matrix is in NED-basis: 322 | elif len(mech) == 3 or len(mech) == 4: 323 | strike, dip, rake = mech[:3] 324 | scalar_moment = 1.0 325 | if len(mech) == 4: 326 | scalar_moment = mech[3] 327 | 328 | rotmat1 = euler_to_matrix( 329 | dip / rad2deg, strike / rad2deg, -rake / rad2deg) 330 | new_M = rotmat1.T * MomentTensor._m_unrot * rotmat1 * scalar_moment 331 | 332 | # to assure right basis system - others are meaningless, provided 333 | # these angles 334 | input_basis = 'NED' 335 | 336 | return basis_transform_matrix(np.matrix(new_M), input_basis, 'NED') 337 | 338 | def _decompose_M(self): 339 | """ 340 | Running the decomposition of the moment tensor object. 341 | 342 | the standard decompositions M = Isotropic + DC + (CLVD or 2nd DC) are 343 | supported (C.f. Jost & Herrmann, Aki & Richards) 344 | """ 345 | k = self._decomposition_key 346 | d = MomentTensor.decomp_dict 347 | if k in d: 348 | d[k][1](self) 349 | 350 | else: 351 | raise MTError('Invalid decomposition key: %i' % k) 352 | 353 | def print_decomposition(self): 354 | for arg in self._decomp_attrib_map_keys: 355 | getter = getattr(self, 'get_' + self._decomp_attrib_map[arg]) 356 | print getter(style='y', system=self._output_basis) 357 | 358 | def _standard_decomposition(self): 359 | """ 360 | Decomposition according Aki & Richards and Jost & Herrmann into 361 | 362 | isotropic + deviatoric 363 | = isotropic + DC + CLVD 364 | 365 | parts of the input moment tensor. 366 | 367 | results are given as attributes, callable via the get_* function: 368 | 369 | DC, CLVD, DC_percentage, seismic_moment, moment_magnitude 370 | """ 371 | 372 | M = self._M 373 | 374 | # isotropic part 375 | M_iso = np.diag(np.array([1. / 3 * np.trace(M), 376 | 1. / 3 * np.trace(M), 377 | 1. / 3 * np.trace(M)])) 378 | 379 | M0_iso = abs(1. / 3 * np.trace(M)) 380 | 381 | # deviatoric part 382 | M_devi = M - M_iso 383 | 384 | self._isotropic = M_iso 385 | self._deviatoric = M_devi 386 | 387 | #eigenvalues and -vectors 388 | eigenwtot, eigenvtot = np.linalg.eig(M_devi) 389 | 390 | # eigenvalues and -vectors of the deviatoric part 391 | eigenw1, eigenv1 = np.linalg.eig(M_devi) 392 | 393 | # eigenvalues in ascending order: 394 | eigenw = np.real(np.take(eigenw1, np.argsort(abs(eigenwtot)))) 395 | eigenv = np.real(np.take(eigenv1, np.argsort(abs(eigenwtot)), 1)) 396 | 397 | # eigenvalues in ascending order in absolute value!!: 398 | eigenw_devi = np.real(np.take(eigenw1, np.argsort(abs(eigenw1)))) 399 | #eigenv_devi = np.real(np.take(eigenv1, np.argsort(abs(eigenw1)), 1)) 400 | 401 | M0_devi = max(abs(eigenw_devi)) 402 | 403 | # named according to Jost & Herrmann: 404 | #a1 = eigenv[:, 0] 405 | a2 = eigenv[:, 1] 406 | a3 = eigenv[:, 2] 407 | 408 | # if only isotropic part exists: 409 | if M0_devi < epsilon: 410 | F = 0.5 411 | else: 412 | F = -eigenw_devi[0] / eigenw_devi[2] 413 | 414 | M_DC = np.matrix(np.zeros((9), float)).reshape(3, 3) 415 | M_CLVD = np.matrix(np.zeros((9), float)).reshape(3, 3) 416 | 417 | M_DC = eigenw[2] * (1 - 2 * F) * (np.outer(a3, a3) - np.outer(a2, a2)) 418 | M_CLVD = M_devi - M_DC 419 | 420 | # according to Bowers & Hudson: 421 | M0 = M0_iso + M0_devi 422 | 423 | M_iso_percentage = int(round(M0_iso / M0 * 100, 6)) 424 | self._iso_percentage = M_iso_percentage 425 | 426 | M_DC_percentage = int(round((1 - 2 * abs(F)) * 427 | (1 - M_iso_percentage / 100.) * 100, 6)) 428 | 429 | self._DC = M_DC 430 | self._CLVD = M_CLVD 431 | self._DC_percentage = M_DC_percentage 432 | 433 | self._seismic_moment = M0 434 | self._moment_magnitude = np.log10( 435 | self._seismic_moment * 1.0e7) / 1.5 - 10.7 436 | 437 | def _decomposition_w_2DC(self): 438 | """ 439 | Decomposition according Aki & Richards and Jost & Herrmann into 440 | 441 | isotropic + deviatoric 442 | = isotropic + DC + DC2 443 | 444 | parts of the input moment tensor. 445 | 446 | results are given as attributes, callable via the get_* function: 447 | 448 | DC1, DC2, DC_percentage, seismic_moment, moment_magnitude 449 | """ 450 | M = self._M 451 | 452 | # isotropic part 453 | M_iso = np.diag(np.array([1. / 3 * np.trace(M), 454 | 1. / 3 * np.trace(M), 455 | 1. / 3 * np.trace(M)])) 456 | 457 | M0_iso = abs(1. / 3 * np.trace(M)) 458 | 459 | # deviatoric part 460 | M_devi = M - M_iso 461 | 462 | self._isotropic = M_iso 463 | self._deviatoric = M_devi 464 | 465 | # eigenvalues and -vectors of the deviatoric part 466 | eigenw1, eigenv1 = np.linalg.eig(M_devi) 467 | 468 | # eigenvalues in ascending order of their absolute values: 469 | eigenw = np.real( 470 | np.take(eigenw1, np.argsort(abs(eigenw1)))) 471 | eigenv = np.real( 472 | np.take(eigenv1, np.argsort(abs(eigenw1)), 1)) 473 | 474 | M0_devi = max(abs(eigenw)) 475 | 476 | # named according to Jost & Herrmann: 477 | a1 = eigenv[:, 0] 478 | a2 = eigenv[:, 1] 479 | a3 = eigenv[:, 2] 480 | 481 | M_DC = np.matrix(np.zeros((9), float)).reshape(3, 3) 482 | M_DC2 = np.matrix(np.zeros((9), float)).reshape(3, 3) 483 | 484 | M_DC = eigenw[2] * (np.outer(a3, a3) - np.outer(a2, a2)) 485 | M_DC2 = eigenw[0] * (np.outer(a1, a1) - np.outer(a2, a2)) 486 | 487 | M_DC_percentage = abs(eigenw[2] / (abs(eigenw[2]) + abs(eigenw[0]))) 488 | 489 | self._DC = M_DC 490 | self._DC2 = M_DC2 491 | self._DC_percentage = M_DC_percentage 492 | 493 | # according to Bowers & Hudson: 494 | M0 = M0_iso + M0_devi 495 | 496 | M_iso_percentage = int(M0_iso / M0 * 100) 497 | self._iso_percentage = M_iso_percentage 498 | 499 | #self._seismic_moment = np.sqrt(1./2*np.sum(eigenw**2) ) 500 | self._seismic_moment = M0 501 | self._moment_magnitude = np.log10( 502 | self._seismic_moment * 1.0e7) / 1.5 - 10.7 503 | 504 | def _decomposition_w_CLVD_2DC(self): 505 | """ 506 | Decomposition according to Dahm (1993) into 507 | 508 | - isotropic 509 | - CLVD 510 | - strike-slip 511 | - dip-slip 512 | 513 | parts of the input moment tensor. 514 | 515 | results are given as attributes, callable via the get_* function: 516 | 517 | iso, CLVD, DC1, DC2, iso_percentage, DC_percentage, DC1_percentage, 518 | DC2_percentage, CLVD_percentage, seismic_moment, moment_magnitude 519 | """ 520 | M = self._M 521 | 522 | # isotropic part 523 | M_iso = np.diag( 524 | np.array([1. / 3 * np.trace(M), 525 | 1. / 3 * np.trace(M), 526 | 1. / 3 * np.trace(M)])) 527 | 528 | #M0_iso = abs(1. / 3 * np.trace(M)) 529 | 530 | # deviatoric part 531 | M_devi = M - M_iso 532 | 533 | self._isotropic = M_iso 534 | self._deviatoric = M_devi 535 | 536 | M_DC1 = np.matrix(np.zeros((9), float)).reshape(3, 3) 537 | M_DC2 = np.matrix(np.zeros((9), float)).reshape(3, 3) 538 | M_CLVD = np.matrix(np.zeros((9), float)).reshape(3, 3) 539 | 540 | M_DC1[0, 0] = -0.5 * (M[1, 1] - M[0, 0]) 541 | M_DC1[1, 1] = 0.5 * (M[1, 1] - M[0, 0]) 542 | M_DC1[0, 1] = M_DC1[1, 0] = M[0, 1] 543 | 544 | M_DC2[0, 2] = M_DC2[2, 0] = M[0, 2] 545 | M_DC2[1, 2] = M_DC2[2, 1] = M[1, 2] 546 | 547 | M_CLVD = 1. / 3. * \ 548 | (0.5 * (M[1, 1] + M[0, 0]) - M[2, 2]) * \ 549 | np.diag(np.array([1., 1., -2.])) 550 | 551 | M_DC = M_DC1 + M_DC2 552 | 553 | self._DC = M_DC 554 | self._DC1 = M_DC1 555 | self._DC2 = M_DC2 556 | 557 | # according to Bowers & Hudson: 558 | eigvals_M, dummy_vecs = np.linalg.eig(M) 559 | eigvals_M_devi, dummy_vecs = np.linalg.eig(M_devi) 560 | eigvals_M_iso, dummy_iso = np.linalg.eig(M_iso) 561 | eigvals_M_clvd, dummy_vecs = np.linalg.eig(M_CLVD) 562 | eigvals_M_dc1, dummy_vecs = np.linalg.eig(M_DC1) 563 | eigvals_M_dc2, dummy_vecs = np.linalg.eig(M_DC2) 564 | 565 | #M0_M = np.max(np.abs(eigvals_M - 1./3*np.sum(eigvals_M) )) 566 | M0_M_iso = np.max( 567 | np.abs(eigvals_M_iso - 1. / 3 * np.sum(eigvals_M))) 568 | M0_M_clvd = np.max( 569 | np.abs(eigvals_M_clvd - 1. / 3 * np.sum(eigvals_M))) 570 | M0_M_dc1 = np.max( 571 | np.abs(eigvals_M_dc1 - 1. / 3 * np.sum(eigvals_M))) 572 | M0_M_dc2 = np.max( 573 | np.abs(eigvals_M_dc2 - 1. / 3 * np.sum(eigvals_M))) 574 | 575 | M0_M_dc = M0_M_dc1 + M0_M_dc2 576 | M0_M_devi = M0_M_clvd + M0_M_dc 577 | M0_M = M0_M_iso + M0_M_devi 578 | 579 | self._iso_percentage = int(M0_M_iso / M0_M * 100) 580 | self._DC_percentage = int(M0_M_dc / M0_M * 100) 581 | self._DC1_percentage = int(M0_M_dc1 / M0_M * 100) 582 | self._DC2_percentage = int(M0_M_dc2 / M0_M * 100) 583 | 584 | #self._seismic_moment = np.sqrt(1./2*np.sum(eigenw**2) ) 585 | self._seismic_moment = M0_M 586 | self._moment_magnitude = np.log10( 587 | self._seismic_moment * 1.0e7) / 1.5 - 10.7 588 | 589 | def _decomposition_w_3DC(self): 590 | """ 591 | Decomposition according Aki & Richards and Jost & Herrmann into 592 | 593 | - isotropic 594 | - deviatoric 595 | - 3 DC 596 | 597 | parts of the input moment tensor. 598 | 599 | results are given as attributes, callable via the get_* function: 600 | 601 | DC1, DC2, DC3, DC_percentage, seismic_moment, moment_magnitude 602 | """ 603 | M = self._M 604 | 605 | # isotropic part 606 | M_iso = np.diag(np.array([1. / 3 * np.trace(M), 607 | 1. / 3 * np.trace(M), 608 | 1. / 3 * np.trace(M)])) 609 | M0_iso = abs(1. / 3 * np.trace(M)) 610 | 611 | # deviatoric part 612 | M_devi = M - M_iso 613 | 614 | self._isotropic = M_iso 615 | self._deviatoric = M_devi 616 | 617 | # eigenvalues and -vectors of the deviatoric part 618 | eigenw1, eigenv1 = np.linalg.eig(M_devi) 619 | M0_devi = max(abs(eigenw1)) 620 | 621 | # eigenvalues and -vectors of the full M !!!!!!!! 622 | eigenw1, eigenv1 = np.linalg.eig(M) 623 | 624 | # eigenvalues in ascending order of their absolute values: 625 | eigenw = np.real( 626 | np.take(eigenw1, np.argsort(abs(eigenw1)))) 627 | eigenv = np.real( 628 | np.take(eigenv1, np.argsort(abs(eigenw1)), 1)) 629 | 630 | # named according to Jost & Herrmann: 631 | a1 = eigenv[:, 0] 632 | a2 = eigenv[:, 1] 633 | a3 = eigenv[:, 2] 634 | 635 | M_DC1 = np.matrix(np.zeros((9), float)).reshape(3, 3) 636 | M_DC2 = np.matrix(np.zeros((9), float)).reshape(3, 3) 637 | M_DC3 = np.matrix(np.zeros((9), float)).reshape(3, 3) 638 | 639 | M_DC1 = 1. / 3. * \ 640 | (eigenw[0] - eigenw[1]) * (np.outer(a1, a1) - np.outer(a2, a2)) 641 | M_DC2 = 1. / 3. * \ 642 | (eigenw[1] - eigenw[2]) * (np.outer(a2, a2) - np.outer(a3, a3)) 643 | M_DC3 = 1. / 3. * \ 644 | (eigenw[2] - eigenw[0]) * (np.outer(a3, a3) - np.outer(a1, a1)) 645 | 646 | M_DC1_perc = int(100 * abs((eigenw[0] - eigenw[1])) / 647 | (abs((eigenw[1] - eigenw[2])) + 648 | abs((eigenw[1] - eigenw[2])) + 649 | abs((eigenw[2] - eigenw[0])))) 650 | M_DC2_perc = int(100 * abs((eigenw[1] - eigenw[2])) / 651 | (abs((eigenw[1] - eigenw[2])) + 652 | abs((eigenw[1] - eigenw[2])) + 653 | abs((eigenw[2] - eigenw[0])))) 654 | 655 | self._DC = M_DC1 656 | self._DC2 = M_DC2 657 | self._DC3 = M_DC3 658 | 659 | self._DC_percentage = M_DC1_perc 660 | self._DC2_percentage = M_DC2_perc 661 | 662 | # according to Bowers & Hudson: 663 | M0 = M0_iso + M0_devi 664 | 665 | M_iso_percentage = int(M0_iso / M0 * 100) 666 | self._iso_percentage = M_iso_percentage 667 | 668 | #self._seismic_moment = np.sqrt(1./2*np.sum(eigenw**2) ) 669 | self._seismic_moment = M0 670 | self._moment_magnitude = np.log10( 671 | self._seismic_moment * 1.0e7) / 1.5 - 10.7 672 | 673 | def _M_to_principal_axis_system(self): 674 | """ 675 | Read in Matrix M and set up eigenvalues (EW) and eigenvectors 676 | (EV) for setting up the principal axis system. 677 | 678 | The internal convention is the 'HNS'-system: H is the 679 | eigenvector for the smallest absolute eigenvalue, S is the 680 | eigenvector for the largest absolute eigenvalue, N is the null 681 | axis. 682 | 683 | Naming due to the geometry: a CLVD is 684 | Symmetric to the S-axis, 685 | Null-axis is common sense, and the third (auxiliary) axis 686 | Helps to construct the R³. 687 | 688 | Additionally builds matrix for basis transformation back to NED system. 689 | 690 | The eigensystem setup defines the colouring order for a later 691 | plotting in the BeachBall class. This order is set by the 692 | '_plot_clr_order' attribute. 693 | """ 694 | 695 | M = self._M 696 | M_devi = self._deviatoric 697 | 698 | # working in framework of 3 principal axes: 699 | # eigenvalues (EW) are in order from high to low 700 | # - neutral axis N, belongs to middle EW 701 | # - symmetry axis S ('sigma') belongs to EW with largest absolute value 702 | # (P- or T-axis) 703 | # - auxiliary axis H ('help') belongs to remaining EW (T- or P-axis) 704 | # EW sorting from lowest to highest value 705 | EW_devi, EV_devi = np.linalg.eigh(M_devi) 706 | EW_order = np.argsort(EW_devi) 707 | 708 | # print 'order',EW_order 709 | 710 | if 1: # self._plot_isotropic_part: 711 | trace_M = np.trace(M) 712 | if abs(trace_M) < epsilon: 713 | trace_M = 0 714 | EW, EV = np.linalg.eigh(M) 715 | for i, ew in enumerate(EW): 716 | if abs(EW[i]) < epsilon: 717 | EW[i] = 0 718 | else: 719 | trace_M = np.trace(M_devi) 720 | if abs(trace_M) < epsilon: 721 | trace_M = 0 722 | 723 | EW, EV = np.linalg.eigh(M_devi) 724 | for i, ew in enumerate(EW): 725 | if abs(EW[i]) < epsilon: 726 | EW[i] = 0 727 | 728 | EW1_devi = EW_devi[EW_order[0]] 729 | EW2_devi = EW_devi[EW_order[1]] 730 | EW3_devi = EW_devi[EW_order[2]] 731 | EV1_devi = EV_devi[:, EW_order[0]] 732 | EV2_devi = EV_devi[:, EW_order[1]] 733 | EV3_devi = EV_devi[:, EW_order[2]] 734 | 735 | EW1 = EW[EW_order[0]] 736 | EW2 = EW[EW_order[1]] 737 | EW3 = EW[EW_order[2]] 738 | EV1 = EV[:, EW_order[0]] 739 | EV2 = EV[:, EW_order[1]] 740 | EV3 = EV[:, EW_order[2]] 741 | 742 | chng_basis_tmp = np.asmatrix(np.zeros((3, 3))) 743 | chng_basis_tmp[:, 0] = EV1_devi 744 | chng_basis_tmp[:, 1] = EV2_devi 745 | chng_basis_tmp[:, 2] = EV3_devi 746 | 747 | symmetry_around_tension = 1 748 | clr = 1 749 | 750 | if abs(EW2_devi) < epsilon: 751 | EW2_devi = 0 752 | 753 | # implosion 754 | if EW1 < 0 and EW2 < 0 and EW3 < 0: 755 | symmetry_around_tension = 0 756 | # logger.debug( 'IMPLOSION - symmetry around pressure axis \n\n') 757 | clr = 1 758 | # explosion 759 | elif EW1 > 0 and EW2 > 0 and EW3 > 0: 760 | symmetry_around_tension = 1 761 | if abs(EW1_devi) > abs(EW3_devi): 762 | symmetry_around_tension = 0 763 | # logger.debug( 'EXPLOSION - symmetry around tension axis \n\n') 764 | clr = -1 765 | # net-implosion 766 | elif EW2 < 0 and sum([EW1, EW2, EW3]) < 0: 767 | if abs(EW1_devi) < abs(EW3_devi): 768 | symmetry_around_tension = 1 769 | clr = 1 770 | else: 771 | symmetry_around_tension = 1 772 | clr = 1 773 | # net-implosion 774 | elif EW2_devi >= 0 and sum([EW1, EW2, EW3]) < 0: 775 | symmetry_around_tension = 0 776 | clr = -1 777 | if abs(EW1_devi) < abs(EW3_devi): 778 | symmetry_around_tension = 1 779 | clr = 1 780 | # net-explosion 781 | elif EW2_devi < 0 and sum([EW1, EW2, EW3]) > 0: 782 | symmetry_around_tension = 1 783 | clr = 1 784 | if abs(EW1_devi) > abs(EW3_devi): 785 | symmetry_around_tension = 0 786 | clr = -1 787 | # net-explosion 788 | elif EW2_devi >= 0 and sum([EW1, EW2, EW3]) > 0: 789 | symmetry_around_tension = 0 790 | clr = -1 791 | else: 792 | # TODO check: this point should never be reached !! 793 | pass 794 | 795 | if abs(EW1_devi) < abs(EW3_devi): 796 | symmetry_around_tension = 1 797 | clr = 1 798 | if 0: # EW2 > 0 :#or (EW2 > 0 and EW2_devi > 0) : 799 | symmetry_around_tension = 0 800 | clr = -1 801 | 802 | if abs(EW1_devi) >= abs(EW3_devi): 803 | symmetry_around_tension = 0 804 | clr = -1 805 | if 0: # EW2 < 0 : 806 | symmetry_around_tension = 1 807 | clr = 1 808 | if (EW3 < 0 and np.trace(self._M) >= 0): 809 | # reaching this point means, we have a serious problem, likely of 810 | # numerical nature 811 | print 'Houston, we have had a problem - check M !!!!!! \n' + \ 812 | '( Trace(M) > 0, but largest eigenvalue is still negative)' 813 | raise MTError(' !! ') 814 | 815 | if trace_M == 0: 816 | # print 'pure deviatoric' 817 | if EW2 == 0: 818 | # print 'pure shear' 819 | symmetry_around_tension = 1 820 | clr = 1 821 | 822 | elif 2 * abs(EW2) == abs(EW1) or 2 * abs(EW2) == abs(EW3): 823 | # print 'pure clvd' 824 | if abs(EW1) < EW3: 825 | # print 'CLVD: symmetry around tension' 826 | symmetry_around_tension = 1 827 | clr = 1 828 | else: 829 | # print 'CLVD: symmetry around pressure' 830 | symmetry_around_tension = 0 831 | clr = -1 832 | else: 833 | # print 'mix of DC and CLVD' 834 | if abs(EW1) < EW3: 835 | # print 'symmetry around tension' 836 | symmetry_around_tension = 1 837 | clr = 1 838 | else: 839 | # print 'symmetry around pressure' 840 | symmetry_around_tension = 0 841 | clr = -1 842 | 843 | # define order of eigenvectors and values according to symmetry axis 844 | if symmetry_around_tension == 1: 845 | EWs = EW3.copy() 846 | EVs = EV3.copy() 847 | EWh = EW1.copy() 848 | EVh = EV1.copy() 849 | 850 | else: 851 | EWs = EW1.copy() 852 | EVs = EV1.copy() 853 | EWh = EW3.copy() 854 | EVh = EV3.copy() 855 | 856 | EWn = EW2 857 | EVn = EV2 858 | 859 | # build the basis system change matrix: 860 | chng_basis = np.asmatrix(np.zeros((3, 3))) 861 | 862 | # order of eigenvector's basis: (H,N,S) 863 | chng_basis[:, 0] = EVh 864 | chng_basis[:, 1] = EVn 865 | chng_basis[:, 2] = EVs 866 | 867 | # matrix for basis transformation 868 | self._rotation_matrix = chng_basis 869 | 870 | # collections of eigenvectors and eigenvalues 871 | self._eigenvectors = [EVh, EVn, EVs] 872 | self._eigenvalues = [EWh, EWn, EWs] 873 | 874 | # principal axes 875 | self._null_axis = EVn 876 | self._t_axis = EV1 877 | self._p_axis = EV3 878 | 879 | # plotting order flag - important for plot in BeachBall class 880 | self._plot_clr_order = clr 881 | 882 | # collection of the faultplanes, given in strike, dip, slip-rake 883 | self._faultplanes = self._find_faultplanes() 884 | 885 | def _find_faultplanes(self): 886 | """ 887 | Sets the two angle-triples, describing the faultplanes of the 888 | Double Couple, defined by the eigenvectors P and T of the 889 | moment tensor object. 890 | 891 | Define a reference Double Couple with strike = dip = 892 | slip-rake = 0, the moment tensor object's DC is transformed 893 | (rotated) w.r.t. this orientation. The respective rotation 894 | matrix yields the first fault plane angles as the Euler 895 | angles. After flipping the first reference plane by 896 | multiplying the appropriate flip-matrix, one gets the second fault 897 | plane's geometry. 898 | 899 | All output angles are in degree 900 | 901 | ( 902 | to check: 903 | using Sebastian's conventions: 904 | 905 | rotationsmatrix1 = 906 | EV Matrix of M, but in order TNP (not as here PNT!!!) 907 | 908 | reference-DC with strike, dip, rake = 0,0,0 909 | in NED - form: M = 0,0,0,0,-1,0 910 | 911 | the eigenvectors of this into a Matrix: 912 | 913 | trafo-matrix2 = EV Matrix of Reference-DC in order TNP 914 | 915 | effective Rotation matrix = (rotation_matrix1 * trafo-matrix2.T).T 916 | 917 | by checking for det <0, make sure, if Matrix must be multiplied by -1 918 | 919 | flip_matrix = 0,0,-1,0,-1,0,-1,0,0 920 | 921 | other DC orientation obtained by flip * effective Rotation matrix 922 | 923 | both matrices in matrix_2_euler 924 | ) 925 | """ 926 | # reference Double Couple (in NED basis) 927 | # it has strike, dip, slip-rake = 0,0,0 928 | refDC = np.matrix([[0., 0., -1.], [0., 0., 0.], [-1., 0., 0.]], 929 | dtype=np.float) 930 | refDC_evals, refDC_evecs = np.linalg.eigh(refDC) 931 | 932 | # matrix which is turning from one fault plane to the other 933 | flip_dc = np.matrix([[0., 0., -1.], [0., -1., 0.], [-1., 0., 0.]], 934 | dtype=np.float) 935 | 936 | # euler-tools need matrices of EV sorted in PNT: 937 | pnt_sorted_EV_matrix = self._rotation_matrix.copy() 938 | 939 | # resort only necessary, if abs(p) <= abs(t) 940 | # print self._plot_clr_order 941 | if self._plot_clr_order < 0: 942 | pnt_sorted_EV_matrix[:, 0] = self._rotation_matrix[:, 2] 943 | pnt_sorted_EV_matrix[:, 2] = self._rotation_matrix[:, 0] 944 | 945 | # rotation matrix, describing the rotation of the eigenvector 946 | # system of the input moment tensor into the eigenvector 947 | # system of the reference Double Couple 948 | rot_matrix_fp1 = (np.dot(pnt_sorted_EV_matrix, refDC_evecs.T)).T 949 | 950 | # check, if rotation has right orientation 951 | if np.linalg.det(rot_matrix_fp1) < 0.: 952 | rot_matrix_fp1 *= -1. 953 | 954 | # adding a rotation into the ambiguous system of the second fault plane 955 | rot_matrix_fp2 = np.dot(flip_dc, rot_matrix_fp1) 956 | 957 | fp1 = self._find_strike_dip_rake(rot_matrix_fp1) 958 | fp2 = self._find_strike_dip_rake(rot_matrix_fp2) 959 | 960 | return [fp1, fp2] 961 | 962 | def _find_strike_dip_rake(self, rotation_matrix): 963 | """ 964 | Returns angles strike, dip, slip-rake in degrees, describing the fault 965 | plane. 966 | """ 967 | (alpha, beta, gamma) = self._matrix_to_euler(rotation_matrix) 968 | return (beta * rad2deg, alpha * rad2deg, -gamma * rad2deg) 969 | 970 | def _cvec(self, x, y, z): 971 | """ 972 | Builds a column vector (matrix type) from a 3 tuple. 973 | """ 974 | return np.matrix([[x, y, z]], dtype=np.float).T 975 | 976 | def _matrix_to_euler(self, rotmat): 977 | """ 978 | Returns three Euler angles alpha, beta, gamma (in radians) from a 979 | rotation matrix. 980 | """ 981 | ex = self._cvec(1., 0., 0.) 982 | ez = self._cvec(0., 0., 1.) 983 | exs = rotmat.T * ex 984 | ezs = rotmat.T * ez 985 | enodes = np.cross(ez.T, ezs.T).T 986 | if np.linalg.norm(enodes) < 1e-10: 987 | enodes = exs 988 | enodess = rotmat * enodes 989 | cos_alpha = float((ez.T * ezs)) 990 | if cos_alpha > 1.: 991 | cos_alpha = 1. 992 | if cos_alpha < -1.: 993 | cos_alpha = -1. 994 | alpha = np.arccos(cos_alpha) 995 | beta = np.mod(np.arctan2(enodes[1, 0], enodes[0, 0]), np.pi * 2.) 996 | gamma = np.mod(-np.arctan2(enodess[1, 0], enodess[0, 0]), np.pi * 2.) 997 | return self._unique_euler(alpha, beta, gamma) 998 | 999 | def _unique_euler(self, alpha, beta, gamma): 1000 | """ 1001 | Uniquify euler angle triplet. 1002 | 1003 | Puts euler angles into ranges compatible with (dip,strike,-rake) in 1004 | seismology: 1005 | 1006 | alpha (dip) : [0, pi/2] 1007 | beta (strike) : [0, 2*pi) 1008 | gamma (-rake) : [-pi, pi) 1009 | 1010 | If alpha is near to zero, beta is replaced by beta+gamma and gamma is 1011 | set to zero, to prevent that additional ambiguity. 1012 | 1013 | If alpha is near to pi/2, beta is put into the range [0,pi). 1014 | """ 1015 | alpha = np.mod(alpha, 2.0 * pi) 1016 | 1017 | if 0.5 * pi < alpha and alpha <= pi: 1018 | alpha = pi - alpha 1019 | beta = beta + pi 1020 | gamma = 2.0 * pi - gamma 1021 | elif pi < alpha and alpha <= 1.5 * pi: 1022 | alpha = alpha - pi 1023 | gamma = pi - gamma 1024 | elif 1.5 * pi < alpha and alpha <= 2.0 * pi: 1025 | alpha = 2.0 * pi - alpha 1026 | beta = beta + pi 1027 | gamma = pi + gamma 1028 | 1029 | alpha = np.mod(alpha, 2.0 * pi) 1030 | beta = np.mod(beta, 2.0 * pi) 1031 | gamma = np.mod(gamma + pi, 2.0 * pi) - pi 1032 | 1033 | # If dip is exactly 90 degrees, one is still 1034 | # free to choose between looking at the plane from either side. 1035 | # Choose to look at such that beta is in the range [0,180) 1036 | 1037 | # This should prevent some problems, when dip is close to 90 degrees: 1038 | if abs(alpha - 0.5 * pi) < 1e-10: 1039 | alpha = 0.5 * pi 1040 | if abs(beta - pi) < 1e-10: 1041 | beta = pi 1042 | if abs(beta - 2. * pi) < 1e-10: 1043 | beta = 0. 1044 | if abs(beta) < 1e-10: 1045 | beta = 0. 1046 | 1047 | if alpha == 0.5 * pi and beta >= pi: 1048 | gamma = -gamma 1049 | beta = np.mod(beta - pi, 2.0 * pi) 1050 | gamma = np.mod(gamma + pi, 2.0 * pi) - pi 1051 | assert 0. <= beta < pi 1052 | assert -pi <= gamma < pi 1053 | 1054 | if alpha < 1e-7: 1055 | beta = np.mod(beta + gamma, 2.0 * pi) 1056 | gamma = 0. 1057 | 1058 | return (alpha, beta, gamma) 1059 | 1060 | def _matrix_w_style_and_system(self, M2return, system, style): 1061 | """ 1062 | Transform matrix into the given basis system 'system'. 1063 | 1064 | If the argument 'style' is set to 'fancy', a 'print' of the return 1065 | value yields a nice shell output of the matrix for better 1066 | visual control. 1067 | """ 1068 | 1069 | M2return = basis_transform_matrix(M2return, 'NED', system.upper()) 1070 | 1071 | if style.lower() in ['f', 'fan', 'fancy', 'y']: 1072 | return fancy_matrix(M2return) 1073 | else: 1074 | return M2return 1075 | 1076 | def _vector_w_style_and_system(self, vectors, system, style='n'): 1077 | """ 1078 | Transform vector(s) into the given basis system 'system'. 1079 | 1080 | If the argument 'style' is set to 'fancy', a 'print' of the return 1081 | value yields a nice shell output of the vector(s) for better 1082 | visual control. 1083 | 1084 | 'vectors' can be either a single array, tuple, matrix or a collection 1085 | in form of a list, array or matrix. 1086 | If it's a list, each entry will be checked, if it's 3D - if not, an 1087 | exception is raised. 1088 | If it's a matrix or array with column-length 3, the columns are 1089 | interpreted as vectors, otherwise, its transposed is used. 1090 | """ 1091 | 1092 | fancy = style.lower() in ['f', 'fan', 'fancy', 'y'] 1093 | 1094 | lo_vectors = [] 1095 | 1096 | # if list of vectors 1097 | if type(vectors) == list: 1098 | lo_vectors = vectors 1099 | 1100 | else: 1101 | assert 3 in vectors.shape 1102 | 1103 | if np.shape(vectors)[0] == 3: 1104 | for ii in np.arange(np.shape(vectors)[1]): 1105 | lo_vectors.append(vectors[:, ii]) 1106 | else: 1107 | for ii in np.arange(np.shape(vectors)[0]): 1108 | lo_vectors.append(vectors[:, ii].transpose()) 1109 | 1110 | lo_vecs_to_show = [] 1111 | for vec in lo_vectors: 1112 | 1113 | t_vec = basis_transform_vector(vec, 'NED', system.upper()) 1114 | 1115 | if fancy: 1116 | lo_vecs_to_show.append(fancy_vector(t_vec)) 1117 | else: 1118 | lo_vecs_to_show.append(t_vec) 1119 | 1120 | if len(lo_vecs_to_show) == 1: 1121 | return lo_vecs_to_show[0] 1122 | 1123 | else: 1124 | if fancy: 1125 | return ''.join(lo_vecs_to_show) 1126 | else: 1127 | return lo_vecs_to_show 1128 | 1129 | def get_M(self, system='NED', style='n'): 1130 | """ 1131 | Returns the moment tensor in matrix representation. 1132 | 1133 | Call with arguments to set ouput in other basis system or in fancy 1134 | style (to be viewed with 'print') 1135 | """ 1136 | return self._matrix_w_style_and_system(self._M, system, style) 1137 | 1138 | def get_decomposition(self, in_system='NED', out_system='NED', style='n'): 1139 | """ 1140 | Returns a tuple of the decomposition results. 1141 | 1142 | Order: 1143 | - 1 - basis of the provided input (string) 1144 | - 2 - basis of the representation (string) 1145 | - 3 - chosen decomposition type (integer) 1146 | 1147 | - 4 - full moment tensor (matrix) 1148 | 1149 | - 5 - isotropic part (matrix) 1150 | - 6 - isotropic percentage (float) 1151 | - 7 - deviatoric part (matrix) 1152 | - 8 - deviatoric percentage (float) 1153 | 1154 | - 9 - DC part (matrix) 1155 | -10 - DC percentage (float) 1156 | -11 - DC2 part (matrix) 1157 | -12 - DC2 percentage (float) 1158 | -13 - DC3 part (matrix) 1159 | -14 - DC3 percentage (float) 1160 | 1161 | -15 - CLVD part (matrix) 1162 | -16 - CLVD percentage (matrix) 1163 | 1164 | -17 - seismic moment (float) 1165 | -18 - moment magnitude (float) 1166 | 1167 | -19 - eigenvectors (3-array) 1168 | -20 - eigenvalues (list) 1169 | -21 - p-axis (3-array) 1170 | -22 - neutral axis (3-array) 1171 | -23 - t-axis (3-array) 1172 | -24 - faultplanes (list of two 3-arrays) 1173 | """ 1174 | return [in_system, out_system, self.get_decomp_type(), 1175 | self.get_M(system=out_system), 1176 | self.get_iso(system=out_system), self.get_iso_percentage(), 1177 | self.get_devi(system=out_system), self.get_devi_percentage(), 1178 | self.get_DC(system=out_system), self.get_DC_percentage(), 1179 | self.get_DC2(system=out_system), self.get_DC2_percentage(), 1180 | self.get_DC3(system=out_system), self.get_DC3_percentage(), 1181 | self.get_CLVD(system=out_system), self.get_CLVD_percentage(), 1182 | self.get_moment(), self.get_mag(), 1183 | self.get_eigvecs(system=out_system), 1184 | self.get_eigvals(system=out_system), 1185 | self.get_p_axis(system=out_system), 1186 | self.get_null_axis(system=out_system), 1187 | self.get_t_axis(system=out_system), 1188 | self.get_fps()] 1189 | 1190 | def __str__(self): 1191 | """ 1192 | Nice compilation of decomposition result to be viewed in the shell 1193 | (call with 'print'). 1194 | """ 1195 | 1196 | mexp = pow(10, np.ceil(np.log10(np.max(np.abs(self._M))))) 1197 | 1198 | m = basis_transform_matrix(self._M / mexp, 'NED', self._output_basis) 1199 | 1200 | def b(i, j): 1201 | x = self._output_basis.lower() 1202 | return x[i] + x[j] 1203 | 1204 | s = '\nScalar Moment: M0 = %g Nm (Mw = %3.1f)\n' 1205 | s += 'Moment Tensor: M%s = %6.3f, M%s = %6.3f, M%s = %6.3f,\n' 1206 | s += ' M%s = %6.3f, M%s = %6.3f, M%s = %6.3f' 1207 | s += ' [ x %g ]\n\n' 1208 | s = s % (self._seismic_moment, self._moment_magnitude, 1209 | b(0, 0), m[0, 0], 1210 | b(1, 1), m[1, 1], 1211 | b(2, 2), m[2, 2], 1212 | b(0, 1), m[0, 1], 1213 | b(0, 2), m[0, 2], 1214 | b(1, 2), m[1, 2], 1215 | mexp) 1216 | 1217 | s += self._fault_planes_as_str() 1218 | return s 1219 | 1220 | def _fault_planes_as_str(self): 1221 | """ 1222 | Internal setup of a nice string, containing information about the fault 1223 | planes. 1224 | """ 1225 | s = '\n' 1226 | for i, sdr in enumerate(self.get_fps()): 1227 | s += 'Fault plane %i: ' % (i + 1) 1228 | s += 'strike = %3.0f°, dip = %3.0f°, slip-rake = %4.0f°\n' % \ 1229 | (sdr[0], sdr[1], sdr[2]) 1230 | return s 1231 | 1232 | def get_input_system(self, style='n', **kwargs): 1233 | """ 1234 | Returns the basis system of the input. 1235 | """ 1236 | return self._input_basis 1237 | 1238 | def get_output_system(self, style='n', **kwargs): 1239 | """ 1240 | Returns the basis system of the output. 1241 | """ 1242 | return self._output_basis 1243 | 1244 | def get_decomp_type(self, style='n', **kwargs): 1245 | """ 1246 | Returns the decomposition type. 1247 | """ 1248 | 1249 | if style == 'y': 1250 | return MomentTensor.decomp_dict[self._decomposition_key][0] 1251 | 1252 | return self._decomposition_key 1253 | 1254 | def get_iso(self, system='NED', style='n'): 1255 | """ 1256 | Returns the isotropic part of the moment tensor in matrix 1257 | representation. 1258 | 1259 | Call with arguments to set ouput in other basis system or in fancy 1260 | style (to be viewed with 'print') 1261 | """ 1262 | return self._matrix_w_style_and_system(self._isotropic, system, style) 1263 | 1264 | def get_devi(self, system='NED', style='n'): 1265 | """ 1266 | Returns the deviatoric part of the moment tensor in matrix 1267 | representation. 1268 | 1269 | Call with arguments to set ouput in other basis system or in fancy 1270 | style (to be viewed with 'print') 1271 | """ 1272 | return self._matrix_w_style_and_system(self._deviatoric, system, style) 1273 | 1274 | def get_DC(self, system='NED', style='n'): 1275 | """ 1276 | Returns the Double Couple part of the moment tensor in matrix 1277 | representation. 1278 | 1279 | Call with arguments to set ouput in other basis system or in fancy 1280 | style (to be viewed with 'print') 1281 | """ 1282 | return self._matrix_w_style_and_system(self._DC, system, style) 1283 | 1284 | def get_DC2(self, system='NED', style='n'): 1285 | """ 1286 | Returns the second Double Couple part of the moment tensor in matrix 1287 | representation. 1288 | 1289 | Call with arguments to set ouput in other basis system or in fancy 1290 | style (to be viewed with 'print') 1291 | """ 1292 | 1293 | if self._DC2 is None: 1294 | if style == 'y': 1295 | return 'not available in this decomposition type' 1296 | else: 1297 | return None 1298 | 1299 | return self._matrix_w_style_and_system(self._DC2, system, style) 1300 | 1301 | def get_DC3(self, system='NED', style='n'): 1302 | """ 1303 | Returns the third Double Couple part of the moment tensor in matrix 1304 | representation. 1305 | 1306 | Call with arguments to set ouput in other basis system or in fancy 1307 | style (to be viewed with 'print') 1308 | """ 1309 | 1310 | if self._DC3 is None: 1311 | if style == 'y': 1312 | return 'not available in this decomposition type' 1313 | else: 1314 | return None 1315 | 1316 | return self._matrix_w_style_and_system(self._DC3, system, style) 1317 | 1318 | def get_CLVD(self, system='NED', style='n'): 1319 | """ 1320 | Returns the CLVD part of the moment tensor in matrix representation. 1321 | 1322 | Call with arguments to set ouput in other basis system or in fancy 1323 | style (to be viewed with 'print') 1324 | """ 1325 | if self._CLVD is None: 1326 | if style == 'y': 1327 | return 'not available in this decomposition type' 1328 | else: 1329 | return None 1330 | 1331 | return self._matrix_w_style_and_system(self._CLVD, system, style) 1332 | 1333 | def get_DC_percentage(self, system='NED', style='n'): 1334 | """ 1335 | Returns the percentage of the DC part of the moment tensor in matrix 1336 | representation. 1337 | """ 1338 | 1339 | return self._DC_percentage 1340 | 1341 | def get_CLVD_percentage(self, system='NED', style='n'): 1342 | """ 1343 | Returns the percentage of the DC part of the moment tensor in matrix 1344 | representation. 1345 | """ 1346 | 1347 | if self._CLVD is None: 1348 | if style == 'y': 1349 | return 'not available in this decomposition type' 1350 | else: 1351 | return None 1352 | 1353 | return int(100 - self._iso_percentage - self._DC_percentage) 1354 | 1355 | def get_DC2_percentage(self, system='NED', style='n'): 1356 | """ 1357 | Returns the percentage of the second DC part of the moment tensor in 1358 | matrix representation. 1359 | """ 1360 | 1361 | if self._DC2 is None: 1362 | if style == 'y': 1363 | return 'not available in this decomposition type' 1364 | else: 1365 | return None 1366 | 1367 | return self._DC2_percentage 1368 | 1369 | def get_DC3_percentage(self, system='NED', style='n'): 1370 | """ 1371 | Returns the percentage of the third DC part of the moment tensor in 1372 | matrix representation. 1373 | """ 1374 | 1375 | if self._DC3 is None: 1376 | if style == 'y': 1377 | return 'not available in this decomposition type' 1378 | else: 1379 | return None 1380 | 1381 | return int(100 - self._DC2_percentage - self._DC_percentage) 1382 | 1383 | def get_iso_percentage(self, system='NED', style='n'): 1384 | """ 1385 | Returns the percentage of the isotropic part of the moment tensor in 1386 | matrix representation. 1387 | """ 1388 | return self._iso_percentage 1389 | 1390 | def get_devi_percentage(self, system='NED', style='n'): 1391 | """ 1392 | Returns the percentage of the deviatoric part of the moment tensor in 1393 | matrix representation. 1394 | """ 1395 | return int(100 - self._iso_percentage) 1396 | 1397 | def get_moment(self, system='NED', style='n'): 1398 | """ 1399 | Returns the seismic moment (in Nm) of the moment tensor. 1400 | """ 1401 | return self._seismic_moment 1402 | 1403 | def get_mag(self, system='NED', style='n'): 1404 | """ 1405 | Returns the moment magnitude M_w of the moment tensor. 1406 | """ 1407 | return self._moment_magnitude 1408 | 1409 | def get_decomposition_key(self, system='NED', style='n'): 1410 | """ 1411 | 10 = standard decomposition (Jost & Herrmann) 1412 | """ 1413 | return self._decomposition_key 1414 | 1415 | def get_eigvals(self, system='NED', style='n', **kwargs): 1416 | """ 1417 | Returns a list of the eigenvalues of the moment tensor. 1418 | """ 1419 | if style == 'y': 1420 | return '%g, %g, %g' % tuple(self._eigenvalues) 1421 | 1422 | # in the order HNS: 1423 | return self._eigenvalues 1424 | 1425 | def get_eigvecs(self, system='NED', style='n'): 1426 | """ 1427 | Returns the eigenvectors of the moment tensor. 1428 | 1429 | Call with arguments to set ouput in other basis system or in fancy 1430 | style (to be viewed with 'print') 1431 | """ 1432 | return self._vector_w_style_and_system(self._eigenvectors, system, 1433 | style) 1434 | 1435 | def get_null_axis(self, system='NED', style='n'): 1436 | """ 1437 | Returns the neutral axis of the moment tensor. 1438 | 1439 | Call with arguments to set ouput in other basis system or in fancy 1440 | style (to be viewed with 'print') 1441 | """ 1442 | 1443 | return self._vector_w_style_and_system(self._null_axis, system, style) 1444 | 1445 | def get_t_axis(self, system='NED', style='n'): 1446 | """ 1447 | Returns the tension axis of the moment tensor. 1448 | 1449 | Call with arguments to set ouput in other basis system or in fancy 1450 | style (to be viewed with 'print') 1451 | """ 1452 | return self._vector_w_style_and_system(self._t_axis, system, style) 1453 | 1454 | def get_p_axis(self, system='NED', style='n'): 1455 | """ 1456 | Returns the pressure axis of the moment tensor. 1457 | 1458 | Call with arguments to set ouput in other basis system or in fancy 1459 | style (to be viewed with 'print') 1460 | """ 1461 | return self._vector_w_style_and_system(self._p_axis, system, style) 1462 | 1463 | def get_transform_matrix(self, system='NED', style='n'): 1464 | """ 1465 | Returns the transformation matrix (input system to principal axis 1466 | system. 1467 | 1468 | Call with arguments to set ouput in other basis system or in fancy 1469 | style (to be viewed with 'print') 1470 | """ 1471 | return self._matrix_w_style_and_system(self._rotation_matrix, system, 1472 | style) 1473 | 1474 | def get_fps(self, **kwargs): 1475 | """ 1476 | Returns a list of the two faultplane 3-tuples, each showing strike, 1477 | dip, slip-rake. 1478 | """ 1479 | fancy_key = kwargs.get('style', '0') 1480 | if fancy_key[0].lower() == 'y': 1481 | return self._fault_planes_as_str() 1482 | else: 1483 | return self._faultplanes 1484 | 1485 | def get_colour_order(self, **kwargs): 1486 | """ 1487 | Returns the value of the plotting order (only important in BeachBall 1488 | instances). 1489 | """ 1490 | return self._plot_clr_order 1491 | 1492 | MomentTensor.decomp_dict = { 1493 | 1: ('ISO + DC + CLVD', 1494 | MomentTensor._standard_decomposition), 1495 | 2: ('ISO + major DC + minor DC', 1496 | MomentTensor._decomposition_w_2DC), 1497 | 3: ('ISO + DC1 + DC2 + DC3', 1498 | MomentTensor._decomposition_w_3DC), 1499 | 4: ('ISO + strike DC + dip DC + CLVD', 1500 | MomentTensor._decomposition_w_CLVD_2DC), 1501 | } 1502 | 1503 | 1504 | def fancy_matrix(m_in): 1505 | m = m_in.copy() 1506 | 1507 | norm_factor = round(max(abs(np.array(m).flatten())), 5) 1508 | 1509 | try: 1510 | if (norm_factor < 0.1) or (norm_factor >= 10): 1511 | if not abs(norm_factor) == 0: 1512 | m = m / norm_factor 1513 | out = "\n / %5.2F %5.2F %5.2F \\\n" % \ 1514 | (m[0, 0], m[0, 1], m[0, 2]) 1515 | out += " | %5.2F %5.2F %5.2F | x %F\n" % \ 1516 | (m[1, 0], m[1, 1], m[1, 2], norm_factor) 1517 | out += " \\ %5.2F %5.2F %5.2F /\n" % \ 1518 | (m[2, 0], m[2, 1], m[2, 2]) 1519 | return out 1520 | except: 1521 | pass 1522 | 1523 | return "\n / %5.2F %5.2F %5.2F \\\n" % (m[0, 0], m[0, 1], m[0, 2]) + \ 1524 | " | %5.2F %5.2F %5.2F | \n" % (m[1, 0], m[1, 1], m[1, 2]) + \ 1525 | " \\ %5.2F %5.2F %5.2F /\n" % (m[2, 0], m[2, 1], m[2, 2]) 1526 | 1527 | 1528 | def fancy_vector(v): 1529 | """ 1530 | Returns a given 3-vector or array in a cute way on the shell, if you 1531 | use 'print' on the return value. 1532 | """ 1533 | return "\n / %5.2F \\\n" % (v[0]) + \ 1534 | " | %5.2F |\n" % (v[1]) + \ 1535 | " \\ %5.2F /\n" % (v[2]) 1536 | 1537 | 1538 | class BeachBall: 1539 | 1540 | """ 1541 | Class for generating a beachball projection for a provided moment tensor 1542 | object. 1543 | 1544 | Input for instance generation: MomentTensor object [,keywords dictionary] 1545 | 1546 | Output can be plots of 1547 | - the eigensystem 1548 | - the complete sphere 1549 | - the projection to a unit sphere 1550 | ... either lower (standard) or upper half 1551 | 1552 | Beside the plots, the unit sphere projection may be saved in a given file. 1553 | 1554 | Alternatively, only the file can be provided without showing anything 1555 | directly. 1556 | """ 1557 | def __init__(self, MT=MomentTensor, kwargs_dict={}, npoints=360): 1558 | self.MT = MT 1559 | self._M = MT._M 1560 | self._set_standard_attributes() 1561 | self._update_attributes(kwargs_dict) 1562 | 1563 | self._plot_n_points = npoints 1564 | self._nodallines_in_NED_system() 1565 | self.arange_1 = np.arange(3 * npoints) - 1 1566 | # self._identify_faultplanes() 1567 | 1568 | def ploBB(self, kwargs, ax=None): 1569 | """ 1570 | Plots the projection of the beachball onto a unit sphere. 1571 | """ 1572 | self._update_attributes(kwargs) 1573 | self._setup_BB() 1574 | self._plot_US(ax=ax) 1575 | 1576 | def save_BB(self, kwargs): 1577 | """ 1578 | Method for saving the 2D projection of the beachball without showing 1579 | the plot. 1580 | 1581 | :param outfile: name of outfile, addressing w.r.t. current directory 1582 | :param format: if no implicit valid format is provided within the 1583 | filename, add file format 1584 | """ 1585 | self._update_attributes(kwargs) 1586 | self._setup_BB() 1587 | self._just_save_bb() 1588 | 1589 | def _just_save_bb(self): 1590 | """ 1591 | Internal method for saving the beachball unit sphere plot into a given 1592 | file. 1593 | 1594 | This method tries to setup the approprite backend according to the 1595 | requested file format first. 'AGG' is used in most cases. 1596 | """ 1597 | import matplotlib 1598 | 1599 | if self._plot_outfile_format == 'svg': 1600 | try: 1601 | matplotlib.use('SVG') 1602 | except: 1603 | matplotlib.use('Agg') 1604 | elif self._plot_outfile_format == 'pdf': 1605 | try: 1606 | matplotlib.use('PDF') 1607 | except: 1608 | matplotlib.use('Agg') 1609 | pass 1610 | elif self._plot_outfile_format == 'ps': 1611 | try: 1612 | matplotlib.use('PS') 1613 | except: 1614 | matplotlib.use('Agg') 1615 | pass 1616 | elif self._plot_outfile_format == 'eps': 1617 | try: 1618 | matplotlib.use('Agg') 1619 | except: 1620 | matplotlib.use('PS') 1621 | pass 1622 | elif self._plot_outfile_format == 'png': 1623 | try: 1624 | matplotlib.use('AGG') 1625 | except: 1626 | mp_out = matplotlib.use('GTKCairo') 1627 | if mp_out: 1628 | mp_out2 = matplotlib.use('Cairo') 1629 | if mp_out2: 1630 | matplotlib.use('GDK') 1631 | 1632 | # finally generating the actual plot 1633 | import pylab as P 1634 | 1635 | plotfig = self._setup_plot_US(P) 1636 | 1637 | outfile_format = self._plot_outfile_format 1638 | outfile_name = self._plot_outfile 1639 | 1640 | outfile_abs_name = os.path.realpath( 1641 | os.path.abspath(os.path.join(os.curdir, outfile_name))) 1642 | 1643 | # save plot into file 1644 | try: 1645 | plotfig.savefig(outfile_abs_name, dpi=self._plot_dpi, 1646 | transparent=True, format=outfile_format) 1647 | except: 1648 | print 'ERROR!! -- Saving of plot not possible' 1649 | return 1650 | P.close(667) 1651 | del P 1652 | del matplotlib 1653 | 1654 | def get_psxy(self, kwargs): 1655 | """ 1656 | Method returning one single string, which can be piped into the psxy 1657 | method of the GMT package. 1658 | 1659 | :param GMT_type: fill/lines/EVs (select type of string), 1660 | default is 'fill' 1661 | :param GMT_scaling: scale the beachball - default radius is 1.0 1662 | :param GMT_tension_colour: tension area of BB - colour flag for -Z in 1663 | psxy, default is 1 1664 | :param GMT_pressure_colour: pressure area of BB - colour flag for -Z in 1665 | psxy, default is 0 1666 | :param GMT_show_2FPs: flag, if both faultplanes are to be shown, 1667 | default is 0 1668 | :param GMT_show_1FP: flag, if one faultplane is to be shown, default 1669 | is 1 1670 | :param GMT_FP_index: 1 or 2, default is 2 1671 | """ 1672 | self._GMT_type = 'fill' 1673 | self._GMT_2fps = False 1674 | self._GMT_1fp = 0 1675 | 1676 | self._GMT_psxy_fill = None 1677 | self._GMT_psxy_nodals = None 1678 | self._GMT_psxy_EVs = None 1679 | self._GMT_scaling = 1. 1680 | 1681 | self._GMT_tension_colour = 1 1682 | self._GMT_pressure_colour = 0 1683 | 1684 | self._update_attributes(kwargs) 1685 | 1686 | self._setup_BB() 1687 | 1688 | self._set_GMT_attributes() 1689 | 1690 | if self._GMT_type == 'fill': 1691 | self._GMT_psxy_fill.seek(0) 1692 | GMT_string = self._GMT_psxy_fill.getvalue() 1693 | elif self._GMT_type == 'lines': 1694 | self._GMT_psxy_nodals.seek(0) 1695 | GMT_string = self._GMT_psxy_nodals.getvalue() 1696 | else: 1697 | GMT_string = self._GMT_psxy_EVs.getvalue() 1698 | 1699 | return GMT_string 1700 | 1701 | def _add_2_GMT_string(self, FH_string, curve, colour): 1702 | """ 1703 | Writes coordinate pair list of given curve as string into temporal 1704 | file handler. 1705 | """ 1706 | colour_Z = colour 1707 | wstring = '> -Z%i\n' % (colour_Z) 1708 | FH_string.write(wstring) 1709 | np.savetxt(FH_string, self._GMT_scaling * curve.transpose()) 1710 | 1711 | def _set_GMT_attributes(self): 1712 | """ 1713 | Set the beachball lines and nodals as strings into a file handler. 1714 | """ 1715 | neg_nodalline = self._nodalline_negative_final_US 1716 | pos_nodalline = self._nodalline_positive_final_US 1717 | FP1_2_plot = self._FP1_final_US 1718 | FP2_2_plot = self._FP2_final_US 1719 | EV_2_plot = self._all_EV_2D_US[:, :2].transpose() 1720 | US = self._unit_sphere 1721 | 1722 | tension_colour = self._GMT_tension_colour 1723 | pressure_colour = self._GMT_pressure_colour 1724 | 1725 | # build strings for possible GMT-output, used by 'psxy' 1726 | GMT_string_FH = StringIO() 1727 | GMT_linestring_FH = StringIO() 1728 | GMT_EVs_FH = StringIO() 1729 | 1730 | self._add_2_GMT_string(GMT_EVs_FH, EV_2_plot, tension_colour) 1731 | GMT_EVs_FH.flush() 1732 | 1733 | if self._plot_clr_order > 0: 1734 | self._add_2_GMT_string(GMT_string_FH, US, pressure_colour) 1735 | self._add_2_GMT_string(GMT_string_FH, neg_nodalline, 1736 | tension_colour) 1737 | self._add_2_GMT_string(GMT_string_FH, pos_nodalline, 1738 | tension_colour) 1739 | GMT_string_FH.flush() 1740 | 1741 | if self._plot_curve_in_curve != 0: 1742 | self._add_2_GMT_string(GMT_string_FH, US, tension_colour) 1743 | 1744 | if self._plot_curve_in_curve < 1: 1745 | self._add_2_GMT_string(GMT_string_FH, neg_nodalline, 1746 | pressure_colour) 1747 | self._add_2_GMT_string(GMT_string_FH, pos_nodalline, 1748 | tension_colour) 1749 | GMT_string_FH.flush() 1750 | else: 1751 | self._add_2_GMT_string(GMT_string_FH, pos_nodalline, 1752 | pressure_colour) 1753 | self._add_2_GMT_string(GMT_string_FH, neg_nodalline, 1754 | tension_colour) 1755 | GMT_string_FH.flush() 1756 | else: 1757 | self._add_2_GMT_string(GMT_string_FH, US, tension_colour) 1758 | self._add_2_GMT_string(GMT_string_FH, neg_nodalline, 1759 | pressure_colour) 1760 | self._add_2_GMT_string(GMT_string_FH, pos_nodalline, 1761 | pressure_colour) 1762 | GMT_string_FH.flush() 1763 | 1764 | if self._plot_curve_in_curve != 0: 1765 | self._add_2_GMT_string(GMT_string_FH, US, pressure_colour) 1766 | if self._plot_curve_in_curve < 1: 1767 | self._add_2_GMT_string(GMT_string_FH, neg_nodalline, 1768 | tension_colour) 1769 | self._add_2_GMT_string(GMT_string_FH, pos_nodalline, 1770 | pressure_colour) 1771 | GMT_string_FH.flush() 1772 | else: 1773 | self._add_2_GMT_string(GMT_string_FH, pos_nodalline, 1774 | tension_colour) 1775 | self._add_2_GMT_string(GMT_string_FH, neg_nodalline, 1776 | pressure_colour) 1777 | 1778 | GMT_string_FH.flush() 1779 | 1780 | # set all nodallines and faultplanes for plotting: 1781 | self._add_2_GMT_string(GMT_linestring_FH, neg_nodalline, 1782 | tension_colour) 1783 | self._add_2_GMT_string(GMT_linestring_FH, pos_nodalline, 1784 | tension_colour) 1785 | 1786 | if self._GMT_2fps: 1787 | self._add_2_GMT_string(GMT_linestring_FH, FP1_2_plot, 1788 | tension_colour) 1789 | self._add_2_GMT_string(GMT_linestring_FH, FP2_2_plot, 1790 | tension_colour) 1791 | 1792 | elif self._GMT_1fp: 1793 | if not int(self._GMT_1fp) in [1, 2]: 1794 | print 'no fault plane specified for being plotted...continue', 1795 | print 'without fault plane(s)' 1796 | pass 1797 | else: 1798 | if int(self._GMT_1fp) == 1: 1799 | self._add_2_GMT_string(GMT_linestring_FH, FP1_2_plot, 1800 | tension_colour) 1801 | else: 1802 | self._add_2_GMT_string(GMT_linestring_FH, FP2_2_plot, 1803 | tension_colour) 1804 | 1805 | self._add_2_GMT_string(GMT_linestring_FH, US, tension_colour) 1806 | 1807 | GMT_linestring_FH.flush() 1808 | 1809 | setattr(self, '_GMT_psxy_nodals', GMT_linestring_FH) 1810 | setattr(self, '_GMT_psxy_fill', GMT_string_FH) 1811 | setattr(self, '_GMT_psxy_EVs', GMT_EVs_FH) 1812 | 1813 | def get_MT(self): 1814 | """ 1815 | Returns the original moment tensor object, handed over to the class at 1816 | generating this instance. 1817 | """ 1818 | return self.MT 1819 | 1820 | def full_sphere_plot(self, kwargs): 1821 | """ 1822 | Plot of the full beachball, projected on a circle with a radius 2. 1823 | """ 1824 | self._update_attributes(kwargs) 1825 | self._setup_BB() 1826 | self._aux_plot() 1827 | 1828 | def _aux_plot(self): 1829 | """ 1830 | Generates the final plot of the total sphere (according to the chosen 1831 | 2D-projection. 1832 | """ 1833 | from matplotlib import interactive 1834 | import pylab as P 1835 | 1836 | P.close('all') 1837 | plotfig = P.figure(665, figsize=(self._plot_aux_plot_size, 1838 | self._plot_aux_plot_size)) 1839 | 1840 | plotfig.subplots_adjust(left=0, bottom=0, right=1, top=1) 1841 | ax = plotfig.add_subplot(111, aspect='equal') 1842 | # P.axis([-1.1,1.1,-1.1,1.1],'equal') 1843 | ax.axison = False 1844 | 1845 | EV_2_plot = getattr(self, '_all_EV' + '_final') 1846 | BV_2_plot = getattr(self, '_all_BV' + '_final').transpose() 1847 | curve_pos_2_plot = getattr(self, '_nodalline_positive' + '_final') 1848 | curve_neg_2_plot = getattr(self, '_nodalline_negative' + '_final') 1849 | FP1_2_plot = getattr(self, '_FP1' + '_final') 1850 | FP2_2_plot = getattr(self, '_FP2' + '_final') 1851 | 1852 | tension_colour = self._plot_tension_colour 1853 | pressure_colour = self._plot_pressure_colour 1854 | 1855 | if self._plot_clr_order > 0: 1856 | if self._plot_fill_flag: 1857 | 1858 | alpha = self._plot_fill_alpha * self._plot_total_alpha 1859 | ax.fill(self._outer_circle[0, :], self._outer_circle[1, :], 1860 | fc=pressure_colour, alpha=alpha) 1861 | ax.fill(curve_pos_2_plot[0, :], curve_pos_2_plot[1, :], 1862 | fc=tension_colour, alpha=alpha) 1863 | ax.fill(curve_neg_2_plot[0, :], curve_neg_2_plot[1, :], 1864 | fc=tension_colour, alpha=alpha) 1865 | 1866 | if self._plot_curve_in_curve != 0: 1867 | ax.fill(self._outer_circle[0, :], self._outer_circle[1, :], 1868 | fc=tension_colour, alpha=alpha) 1869 | if self._plot_curve_in_curve < 1: 1870 | ax.fill(curve_neg_2_plot[0, :], curve_neg_2_plot[1, :], 1871 | fc=pressure_colour, alpha=alpha) 1872 | ax.fill(curve_pos_2_plot[0, :], curve_pos_2_plot[1, :], 1873 | fc=tension_colour, alpha=alpha) 1874 | else: 1875 | ax.fill(curve_pos_2_plot[0, :], curve_pos_2_plot[1, :], 1876 | fc=pressure_colour, alpha=alpha) 1877 | ax.fill(curve_neg_2_plot[0, :], curve_neg_2_plot[1, :], 1878 | fc=tension_colour, alpha=alpha) 1879 | 1880 | if self._plot_show_princ_axes: 1881 | alpha = self._plot_princ_axes_alpha * self._plot_total_alpha 1882 | ax.plot([EV_2_plot[0, 0]], [EV_2_plot[1, 0]], 'm^', 1883 | ms=self._plot_princ_axes_symsize, 1884 | lw=self._plot_princ_axes_lw, alpha=alpha) 1885 | ax.plot([EV_2_plot[0, 3]], [EV_2_plot[1, 3]], 'mv', 1886 | ms=self._plot_princ_axes_symsize, 1887 | lw=self._plot_princ_axes_lw, alpha=alpha) 1888 | ax.plot([EV_2_plot[0, 1]], [EV_2_plot[1, 1]], 'b^', 1889 | ms=self._plot_princ_axes_symsize, 1890 | lw=self._plot_princ_axes_lw, alpha=alpha) 1891 | ax.plot([EV_2_plot[0, 4]], [EV_2_plot[1, 4]], 'bv', 1892 | ms=self._plot_princ_axes_symsize, 1893 | lw=self._plot_princ_axes_lw, alpha=alpha) 1894 | ax.plot([EV_2_plot[0, 2]], [EV_2_plot[1, 2]], 'g^', 1895 | ms=self._plot_princ_axes_symsize, 1896 | lw=self._plot_princ_axes_lw, alpha=alpha) 1897 | ax.plot([EV_2_plot[0, 5]], [EV_2_plot[1, 5]], 'gv', 1898 | ms=self._plot_princ_axes_symsize, 1899 | lw=self._plot_princ_axes_lw, alpha=alpha) 1900 | else: 1901 | if self._plot_fill_flag: 1902 | alpha = self._plot_fill_alpha * self._plot_total_alpha 1903 | ax.fill(self._outer_circle[0, :], self._outer_circle[1, :], 1904 | fc=tension_colour, alpha=alpha) 1905 | ax.fill(curve_pos_2_plot[0, :], curve_pos_2_plot[1, :], 1906 | fc=pressure_colour, alpha=alpha) 1907 | ax.fill(curve_neg_2_plot[0, :], curve_neg_2_plot[1, :], 1908 | fc=pressure_colour, alpha=alpha) 1909 | 1910 | if self._plot_curve_in_curve != 0: 1911 | ax.fill(self._outer_circle[0, :], self._outer_circle[1, :], 1912 | fc=pressure_colour, alpha=alpha) 1913 | if self._plot_curve_in_curve < 0: 1914 | ax.fill(curve_neg_2_plot[0, :], curve_neg_2_plot[1, :], 1915 | fc=tension_colour, alpha=alpha) 1916 | ax.fill(curve_pos_2_plot[0, :], curve_pos_2_plot[1, :], 1917 | fc=pressure_colour, alpha=alpha) 1918 | pass 1919 | else: 1920 | ax.fill(curve_pos_2_plot[0, :], curve_pos_2_plot[1, :], 1921 | fc=tension_colour, alpha=alpha) 1922 | ax.fill(curve_neg_2_plot[0, :], curve_neg_2_plot[1, :], 1923 | fc=pressure_colour, alpha=alpha) 1924 | pass 1925 | 1926 | if self._plot_show_princ_axes: 1927 | alpha = self._plot_princ_axes_alpha * self._plot_total_alpha 1928 | ax.plot([EV_2_plot[0, 0]], [EV_2_plot[1, 0]], 'g^', 1929 | ms=self._plot_princ_axes_symsize, 1930 | lw=self._plot_princ_axes_lw, alpha=alpha) 1931 | ax.plot([EV_2_plot[0, 3]], [EV_2_plot[1, 3]], 'gv', 1932 | ms=self._plot_princ_axes_symsize, 1933 | lw=self._plot_princ_axes_lw, alpha=alpha) 1934 | ax.plot([EV_2_plot[0, 1]], [EV_2_plot[1, 1]], 'b^', 1935 | ms=self._plot_princ_axes_symsize, 1936 | lw=self._plot_princ_axes_lw, alpha=alpha) 1937 | ax.plot([EV_2_plot[0, 4]], [EV_2_plot[1, 4]], 'bv', 1938 | ms=self._plot_princ_axes_symsize, 1939 | lw=self._plot_princ_axes_lw, alpha=alpha) 1940 | ax.plot([EV_2_plot[0, 2]], [EV_2_plot[1, 2]], 'm^', 1941 | ms=self._plot_princ_axes_symsize, 1942 | lw=self._plot_princ_axes_lw, alpha=alpha) 1943 | ax.plot([EV_2_plot[0, 5]], [EV_2_plot[1, 5]], 'mv', 1944 | ms=self._plot_princ_axes_symsize, 1945 | lw=self._plot_princ_axes_lw, alpha=alpha) 1946 | 1947 | self._plot_nodalline_colour = 'y' 1948 | 1949 | ax.plot(curve_neg_2_plot[0, :], curve_neg_2_plot[1, :], 'o', 1950 | c=self._plot_nodalline_colour, lw=self._plot_nodalline_width, 1951 | alpha=self._plot_nodalline_alpha * self._plot_total_alpha, 1952 | ms=3) 1953 | 1954 | self._plot_nodalline_colour = 'b' 1955 | 1956 | ax.plot(curve_pos_2_plot[0, :], curve_pos_2_plot[1, :], 'D', 1957 | c=self._plot_nodalline_colour, lw=self._plot_nodalline_width, 1958 | alpha=self._plot_nodalline_alpha * self._plot_total_alpha, 1959 | ms=3) 1960 | 1961 | if self._plot_show_1faultplane: 1962 | if self._plot_show_FP_index == 1: 1963 | ax.plot(FP1_2_plot[0, :], FP1_2_plot[1, :], '+', 1964 | c=self._plot_faultplane_colour, 1965 | lw=self._plot_faultplane_width, 1966 | alpha=self._plot_faultplane_alpha * 1967 | self._plot_total_alpha, ms=5) 1968 | elif self._plot_show_FP_index == 2: 1969 | ax.plot(FP2_2_plot[0, :], FP2_2_plot[1, :], '+', 1970 | c=self._plot_faultplane_colour, 1971 | lw=self._plot_faultplane_width, 1972 | alpha=self._plot_faultplane_alpha * 1973 | self._plot_total_alpha, ms=5) 1974 | 1975 | elif self._plot_show_faultplanes: 1976 | ax.plot(FP1_2_plot[0, :], FP1_2_plot[1, :], '+', 1977 | c=self._plot_faultplane_colour, 1978 | lw=self._plot_faultplane_width, 1979 | alpha=self._plot_faultplane_alpha * self._plot_total_alpha, 1980 | ms=4) 1981 | ax.plot(FP2_2_plot[0, :], FP2_2_plot[1, :], '+', 1982 | c=self._plot_faultplane_colour, 1983 | lw=self._plot_faultplane_width, 1984 | alpha=self._plot_faultplane_alpha * self._plot_total_alpha, 1985 | ms=4) 1986 | else: 1987 | pass 1988 | 1989 | # if isotropic part shall be displayed, fill the circle completely with 1990 | # the appropriate colour 1991 | if self._pure_isotropic: 1992 | if abs(np.trace(self._M)) > epsilon: 1993 | if self._plot_clr_order < 0: 1994 | ax.fill(self._outer_circle[0, :], self._outer_circle[1, :], 1995 | fc=tension_colour, alpha=1, zorder=100) 1996 | else: 1997 | ax.fill(self._outer_circle[0, :], self._outer_circle[1, :], 1998 | fc=pressure_colour, alpha=1, zorder=100) 1999 | 2000 | # plot NED basis vectors 2001 | if self._plot_show_basis_axes: 2002 | plot_size_in_points = self._plot_size * 2.54 * 72 2003 | points_per_unit = plot_size_in_points / 2. 2004 | 2005 | fontsize = plot_size_in_points / 66. 2006 | symsize = plot_size_in_points / 77. 2007 | 2008 | direction_letters = list('NSEWDU') 2009 | for idx, val in enumerate(BV_2_plot): 2010 | x_coord = val[0] 2011 | y_coord = val[1] 2012 | np_letter = direction_letters[idx] 2013 | 2014 | rot_angle = -np.arctan2(y_coord, x_coord) + pi / 2. 2015 | original_rho = np.sqrt(x_coord ** 2 + y_coord ** 2) 2016 | 2017 | marker_x = (original_rho - (3 * symsize / points_per_unit)) * \ 2018 | np.sin(rot_angle) 2019 | marker_y = (original_rho - (3 * symsize / points_per_unit)) * \ 2020 | np.cos(rot_angle) 2021 | annot_x = (original_rho - (8.5 * fontsize / points_per_unit)) \ 2022 | * np.sin(rot_angle) 2023 | annot_y = (original_rho - (8.5 * fontsize / points_per_unit)) \ 2024 | * np.cos(rot_angle) 2025 | 2026 | ax.text(annot_x, annot_y, np_letter, 2027 | horizontalalignment='center', size=fontsize, 2028 | weight='bold', verticalalignment='center', 2029 | bbox=dict(edgecolor='white', facecolor='white', 2030 | alpha=1)) 2031 | 2032 | if original_rho > epsilon: 2033 | ax.scatter([marker_x], [marker_y], 2034 | marker=(3, 0, rot_angle), s=symsize ** 2, c='k', 2035 | facecolor='k', zorder=300) 2036 | else: 2037 | ax.scatter([x_coord], [y_coord], marker=(4, 1, rot_angle), 2038 | s=symsize ** 2, c='k', facecolor='k', 2039 | zorder=300) 2040 | 2041 | # plot both circle lines (radius 1 and 2) 2042 | ax.plot(self._unit_sphere[0, :], self._unit_sphere[1, :], 2043 | c=self._plot_outerline_colour, lw=self._plot_outerline_width, 2044 | alpha=self._plot_outerline_alpha * self._plot_total_alpha) 2045 | ax.plot(self._outer_circle[0, :], self._outer_circle[1, :], 2046 | c=self._plot_outerline_colour, lw=self._plot_outerline_width, 2047 | alpha=self._plot_outerline_alpha * self._plot_total_alpha) 2048 | 2049 | # dummy points for setting plot plot size more accurately 2050 | ax.plot([0, 2.1, 0, -2.1], [2.1, 0, -2.1, 0], ',', alpha=0.) 2051 | 2052 | ax.autoscale_view(tight=True, scalex=True, scaley=True) 2053 | interactive(True) 2054 | 2055 | if self._plot_save_plot: 2056 | try: 2057 | plotfig.savefig(self._plot_outfile + '.' + 2058 | self._plot_outfile_format, dpi=self._plot_dpi, 2059 | transparent=True, 2060 | format=self._plot_outfile_format) 2061 | except: 2062 | print 'saving of plot not possible' 2063 | 2064 | P.show() 2065 | 2066 | def pa_plot(self, kwargs): 2067 | """ 2068 | Plot of the solution in the principal axes system. 2069 | """ 2070 | import pylab as P 2071 | 2072 | self._update_attributes(kwargs) 2073 | 2074 | r_hor = self._r_hor_for_pa_plot 2075 | r_hor_FP = self._r_hor_FP_for_pa_plot 2076 | 2077 | P.rc('grid', color='#316931', linewidth=0.5, linestyle='-.') 2078 | P.rc('xtick', labelsize=12) 2079 | P.rc('ytick', labelsize=10) 2080 | 2081 | width, height = P.rcParams['figure.figsize'] 2082 | size = min(width, height) 2083 | 2084 | fig = P.figure(34, figsize=(size, size)) 2085 | P.clf() 2086 | ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True, axisbg='#d5de9c') 2087 | 2088 | r_steps = [0.000001] 2089 | for i in (np.arange(4) + 1) * 0.2: 2090 | r_steps.append(i) 2091 | r_labels = ['S'] 2092 | for ii in xrange(len(r_steps)): 2093 | if (ii + 1) % 2 == 0: 2094 | r_labels.append(str(r_steps[ii])) 2095 | else: 2096 | r_labels.append(' ') 2097 | 2098 | t_angles = np.arange(0., 360., 90) 2099 | t_labels = [' N ', ' H ', ' - N', ' - H'] 2100 | 2101 | P.thetagrids(t_angles, labels=t_labels) 2102 | 2103 | ax.plot(self._phi_curve, r_hor, color='r', lw=3) 2104 | ax.plot(self._phi_curve, r_hor_FP, color='b', lw=1.5) 2105 | ax.set_rmax(1.0) 2106 | P.grid(True) 2107 | 2108 | P.rgrids((r_steps), labels=r_labels) 2109 | 2110 | ax.set_title("beachball in eigenvector system", fontsize=15) 2111 | 2112 | if self._plot_save_plot: 2113 | try: 2114 | fig.savefig(self._plot_outfile + '.' + 2115 | self._plot_outfile_format, dpi=self._plot_dpi, 2116 | transparent=True, 2117 | format=self._plot_outfile_format) 2118 | except: 2119 | print 'saving of plot not possible' 2120 | P.show() 2121 | 2122 | def _set_standard_attributes(self): 2123 | """ 2124 | Sets default values of mandatory arguments. 2125 | """ 2126 | # plot basis system and view point: 2127 | self._plot_basis = 'NED' 2128 | self._plot_projection = 'stereo' 2129 | self._plot_viewpoint = [0., 0., 0.] 2130 | self._plot_basis_change = None 2131 | 2132 | # flag, if upper hemisphere is seen instead 2133 | self._plot_show_upper_hemis = False 2134 | 2135 | # flag, if isotropic part shall be considered 2136 | self._plot_isotropic_part = False 2137 | self._pure_isotropic = False 2138 | 2139 | # number of minimum points per line and full circle (number/360 is 2140 | # minimum of points per degree at rounded lines) 2141 | self._plot_n_points = 360 2142 | 2143 | # nodal line of pressure and tension regimes: 2144 | self._plot_nodalline_width = 2 2145 | self._plot_nodalline_colour = 'k' 2146 | self._plot_nodalline_alpha = 1. 2147 | 2148 | # outer circle line 2149 | self._plot_outerline_width = 2 2150 | self._plot_outerline_colour = 'k' 2151 | self._plot_outerline_alpha = 1. 2152 | 2153 | # faultplane(s) 2154 | self._plot_faultplane_width = 4 2155 | self._plot_faultplane_colour = 'b' 2156 | self._plot_faultplane_alpha = 1. 2157 | 2158 | self._plot_show_faultplanes = False 2159 | self._plot_show_1faultplane = False 2160 | self._plot_show_FP_index = 1 2161 | 2162 | # principal axes: 2163 | self._plot_show_princ_axes = False 2164 | self._plot_princ_axes_symsize = 10 2165 | self._plot_princ_axes_lw = 3 2166 | self._plot_princ_axes_alpha = 0.5 2167 | 2168 | # NED basis: 2169 | self._plot_show_basis_axes = False 2170 | 2171 | # filling of the area: 2172 | self._plot_clr_order = self.MT.get_colour_order() 2173 | self._plot_curve_in_curve = 0 2174 | self._plot_fill_flag = True 2175 | self._plot_tension_colour = 'r' 2176 | self._plot_pressure_colour = 'w' 2177 | self._plot_fill_alpha = 1. 2178 | 2179 | # general plot options 2180 | self._plot_size = 5 2181 | self._plot_aux_plot_size = 5 2182 | self._plot_dpi = 200 2183 | 2184 | self._plot_total_alpha = 1. 2185 | 2186 | # possibility to add external data (e.g. measured polariations) 2187 | self._plot_external_data = False 2188 | self._external_data = None 2189 | 2190 | # if, howto, whereto save the plot 2191 | self._plot_save_plot = False 2192 | self._plot_outfile = './BB_plot_example' 2193 | self._plot_outfile_format = 'svg' 2194 | 2195 | def _update_attributes(self, kwargs): 2196 | """ 2197 | Makes an internal update of the object's attributes with the 2198 | provided list of keyword arguments. 2199 | 2200 | If the keyword (extended by a leading _ ) is in the dict of 2201 | the object, the value is updated. Otherwise, the keyword is 2202 | ignored. 2203 | """ 2204 | for key in kwargs.keys(): 2205 | if key[0] == '_': 2206 | kw = key[1:] 2207 | else: 2208 | kw = key 2209 | if '_' + kw in dir(self): 2210 | setattr(self, '_' + kw, kwargs[key]) 2211 | 2212 | if kwargs.get('plot_only_lines', False): 2213 | setattr(self, '_plot_fill_flag', False) 2214 | 2215 | def _setup_BB(self, unit_circle=True): 2216 | """ 2217 | Setup of the beachball, when a plotting method is evoked. 2218 | 2219 | Contains all the technical stuff for generating the final view of the 2220 | beachball: 2221 | 2222 | - Finding a rotation matrix, describing the given viewpoint onto the 2223 | beachball projection 2224 | - Rotating all elements (lines, points) w.r.t. the given viewpoint 2225 | - Projecting the 3D sphere into the 2D plane 2226 | - Building circle lines in radius r=1 and r=2 2227 | - Correct the order of line points, yielding a consecutive set of 2228 | points for drawing lines 2229 | - Smoothing of all curves, avoiding nasty sectioning connection lines 2230 | - Checking, if the two nodalline curves are laying completely within 2231 | each other ( cahnges plotting order of overlay plot construction) 2232 | - Projection of final smooth solution onto the standard unit sphere 2233 | """ 2234 | self._find_basis_change_2_new_viewpoint() 2235 | self._rotate_all_objects_2_new_view() 2236 | self._vertical_2D_projection() 2237 | 2238 | if unit_circle: 2239 | self._build_circles() 2240 | 2241 | if not self.MT._iso_percentage == 100: 2242 | self._correct_curves() 2243 | self._smooth_curves() 2244 | self._check_curve_in_curve() 2245 | 2246 | self._projection_2_unit_sphere() 2247 | 2248 | if self.MT._iso_percentage == 100: 2249 | if np.trace(self.MT.get_M()) < 0: 2250 | self._plot_clr_order = 1 2251 | else: 2252 | self._plot_clr_order = -1 2253 | 2254 | def _correct_curves(self): 2255 | """ 2256 | Correcting potentially wrong curves. 2257 | 2258 | Checks, if the order of the given coordinates of the lines must be 2259 | re-arranged, allowing for an automatical line plotting. 2260 | """ 2261 | list_of_curves_2_correct = ['nodalline_negative', 'nodalline_positive', 2262 | 'FP1', 'FP2'] 2263 | n_curve_points = self._plot_n_points 2264 | 2265 | for obj in list_of_curves_2_correct: 2266 | obj2cor_name = '_' + obj + '_2D' 2267 | obj2cor = getattr(self, obj2cor_name) 2268 | 2269 | obj2cor_in_right_order = self._sort_curve_points(obj2cor) 2270 | 2271 | # logger.debug( 'curve: ', str(obj)) 2272 | # check, if curve closed !!!!!! 2273 | start_r = np.sqrt(obj2cor_in_right_order[0, 0] ** 2 + 2274 | obj2cor_in_right_order[1, 0] ** 2) 2275 | r_last_point = np.sqrt(obj2cor_in_right_order[0, -1] ** 2 + 2276 | obj2cor_in_right_order[1, -1] ** 2) 2277 | dist_last_first_point = \ 2278 | np.sqrt((obj2cor_in_right_order[0, -1] - 2279 | obj2cor_in_right_order[0, 0]) ** 2 + 2280 | (obj2cor_in_right_order[1, -1] - 2281 | obj2cor_in_right_order[1, 0]) ** 2) 2282 | 2283 | # check, if distance between last and first point is smaller than 2284 | # the distance between last point and the edge (at radius=2) 2285 | if dist_last_first_point > (2 - r_last_point): 2286 | # add points on edge to polygon, if it is an open curve 2287 | # logger.debug( str(obj)+' not closed - closing over edge... ') 2288 | phi_end = np.arctan2(obj2cor_in_right_order[0, -1], 2289 | obj2cor_in_right_order[1, -1]) % (2 * pi) 2290 | R_end = r_last_point 2291 | phi_start = np.arctan2(obj2cor_in_right_order[0, 0], 2292 | obj2cor_in_right_order[1, 0]) % (2 * pi) 2293 | R_start = start_r 2294 | 2295 | # add one point on the edge every fraction of degree given by 2296 | # input parameter, increase the radius linearily 2297 | phi_end_larger = np.sign(phi_end - phi_start) 2298 | angle_smaller_pi = np.sign(pi - np.abs(phi_end - phi_start)) 2299 | 2300 | if phi_end_larger * angle_smaller_pi > 0: 2301 | go_ccw = True 2302 | openangle = (phi_end - phi_start) % (2 * pi) 2303 | else: 2304 | go_ccw = False 2305 | openangle = (phi_start - phi_end) % (2 * pi) 2306 | 2307 | radius_interval = R_start - R_end # closing from end to start 2308 | 2309 | n_edgepoints = int(openangle * rad2deg * 2310 | n_curve_points / 360.) - 1 2311 | # logger.debug( 'open angle %.2f degrees - filling with %i 2312 | # points on the edge\n'%(openangle/pi*180,n_edgepoints)) 2313 | if go_ccw: 2314 | obj2cor_in_right_order = \ 2315 | list(obj2cor_in_right_order.transpose()) 2316 | for kk in xrange(n_edgepoints + 1): 2317 | current_phi = phi_end - kk * openangle / \ 2318 | (n_edgepoints + 1) 2319 | current_radius = R_end + kk * radius_interval / \ 2320 | (n_edgepoints + 1) 2321 | temp = [current_radius * math.sin(current_phi), 2322 | current_radius * np.cos(current_phi)] 2323 | obj2cor_in_right_order.append(temp) 2324 | obj2cor_in_right_order = \ 2325 | np.array(obj2cor_in_right_order).transpose() 2326 | else: 2327 | obj2cor_in_right_order = \ 2328 | list(obj2cor_in_right_order.transpose()) 2329 | for kk in xrange(n_edgepoints + 1): 2330 | current_phi = phi_end + kk * openangle / \ 2331 | (n_edgepoints + 1) 2332 | current_radius = R_end + kk * radius_interval / \ 2333 | (n_edgepoints + 1) 2334 | temp = [current_radius * math.sin(current_phi), 2335 | current_radius * np.cos(current_phi)] 2336 | obj2cor_in_right_order.append(temp) 2337 | obj2cor_in_right_order = \ 2338 | np.array(obj2cor_in_right_order).transpose() 2339 | setattr(self, '_' + obj + '_in_order', obj2cor_in_right_order) 2340 | return 1 2341 | 2342 | def _nodallines_in_NED_system(self): 2343 | """ 2344 | The two nodal lines between the areas on a beachball are given by the 2345 | points, where tan²(alpha) = (-EWs/(EWN*cos(phi)**2 + EWh*sin(phi)**2)) 2346 | is fulfilled. 2347 | 2348 | This solution is gained in the principal axes system and then expressed 2349 | in terms of the NED basis system 2350 | 2351 | output: 2352 | - set of points, building the first nodal line, coordinates in the 2353 | input basis system (standard NED) 2354 | - set of points, building the second nodal line, coordinates in the 2355 | input basis system (standard NED) 2356 | - array with 6 points, describing positive and negative part of 3 2357 | principal axes 2358 | - array with partition of full circle (angle values in degrees) 2359 | fraction is given by parametre n_curve_points 2360 | """ 2361 | # build the nodallines of positive/negative areas in the principal axes 2362 | # system 2363 | n_curve_points = self._plot_n_points 2364 | 2365 | # phi is the angle between neutral axis and horizontal projection 2366 | # of the curve point to the surface, spanned by H- and 2367 | # N-axis. Running mathematically negative (clockwise) around the 2368 | # SIGMA-axis. Stepsize is given by the parametre for number of 2369 | # curve points 2370 | phi = (np.arange(n_curve_points) / float(n_curve_points) + 2371 | 1. / n_curve_points) * 2 * pi 2372 | self._phi_curve = phi 2373 | 2374 | # analytical/geometrical solution for separatrix curve - alpha is 2375 | # opening angle between principal axis SIGMA and point of curve. (alpha 2376 | # is 0, if curve lies directly on the SIGMA axis) 2377 | 2378 | # CASE: including isotropic part 2379 | # sigma axis flippes, if EWn flippes sign 2380 | 2381 | EWh_devi = self.MT.get_eigvals()[0] - 1. / 3 * np.trace(self._M) 2382 | EWn_devi = self.MT.get_eigvals()[1] - 1. / 3 * np.trace(self._M) 2383 | EWs_devi = self.MT.get_eigvals()[2] - 1. / 3 * np.trace(self._M) 2384 | 2385 | if not self._plot_isotropic_part: 2386 | EWh = EWh_devi 2387 | EWn = EWn_devi 2388 | EWs = EWs_devi 2389 | else: 2390 | EWh_tmp = self.MT.get_eigvals()[0] 2391 | EWn_tmp = self.MT.get_eigvals()[1] 2392 | EWs_tmp = self.MT.get_eigvals()[2] 2393 | 2394 | trace_m = np.sum(self.MT.get_eigvals()) 2395 | EWh = EWh_tmp.copy() 2396 | EWs = EWs_tmp.copy() 2397 | 2398 | if trace_m != 0: 2399 | if (self._plot_clr_order > 0 and EWn_tmp >= 0 and 2400 | abs(EWs_tmp) > abs(EWh_tmp)) or \ 2401 | (self._plot_clr_order < 0 and 2402 | EWn_tmp <= 0 and abs(EWs_tmp) > abs(EWh_tmp)): 2403 | 2404 | EWs = EWh_tmp.copy() 2405 | EWh = EWs_tmp.copy() 2406 | print 'changed order!!\n' 2407 | EVs_tmp = self.MT._rotation_matrix[:, 2].copy() 2408 | EVh_tmp = self.MT._rotation_matrix[:, 0].copy() 2409 | 2410 | self.MT._rotation_matrix[:, 0] = EVs_tmp 2411 | self.MT._rotation_matrix[:, 2] = EVh_tmp 2412 | self._plot_clr_order *= -1 2413 | 2414 | EWn = EWn_tmp.copy() 2415 | 2416 | if abs(EWn) < epsilon: 2417 | EWn = 0 2418 | norm_factor = max(np.abs([EWh, EWn, EWs])) 2419 | 2420 | [EWh, EWn, EWs] = [xx / norm_factor for xx in [EWh, EWn, EWs]] 2421 | 2422 | RHS = -EWs / (EWn * np.cos(phi) ** 2 + EWh * np.sin(phi) ** 2) 2423 | 2424 | if np.all([np.sign(xx) >= 0 for xx in RHS]): 2425 | alpha = np.arctan(np.sqrt(RHS)) * rad2deg 2426 | else: 2427 | alpha = phi.copy() 2428 | alpha[:] = 90 2429 | self._pure_isotropic = 1 2430 | 2431 | # fault planes: 2432 | RHS_FP = 1. / (np.sin(phi) ** 2) 2433 | alpha_FP = np.arctan(np.sqrt(RHS_FP)) * rad2deg 2434 | 2435 | # horizontal coordinates of curves 2436 | r_hor = np.sin(alpha / rad2deg) 2437 | r_hor_FP = np.sin(alpha_FP / rad2deg) 2438 | 2439 | self._r_hor_for_pa_plot = r_hor 2440 | self._r_hor_FP_for_pa_plot = r_hor_FP 2441 | 2442 | H_values = np.sin(phi) * r_hor 2443 | N_values = np.cos(phi) * r_hor 2444 | H_values_FP = np.sin(phi) * r_hor_FP 2445 | N_values_FP = np.cos(phi) * r_hor_FP 2446 | 2447 | # set vertical value of curve point coordinates - two symmetric curves 2448 | # exist 2449 | S_values_positive = np.cos(alpha / rad2deg) 2450 | S_values_negative = -np.cos(alpha / rad2deg) 2451 | S_values_positive_FP = np.cos(alpha_FP / rad2deg) 2452 | S_values_negative_FP = -np.cos(alpha_FP / rad2deg) 2453 | 2454 | # change basis back to original input reference system 2455 | chng_basis = self.MT._rotation_matrix 2456 | 2457 | line_tuple_pos = np.zeros((3, n_curve_points)) 2458 | line_tuple_neg = np.zeros((3, n_curve_points)) 2459 | 2460 | for ii in xrange(n_curve_points): 2461 | pos_vec_in_EV_basis = np.array([H_values[ii], N_values[ii], 2462 | S_values_positive[ii]]).transpose() 2463 | neg_vec_in_EV_basis = np.array([H_values[ii], N_values[ii], 2464 | S_values_negative[ii]]).transpose() 2465 | line_tuple_pos[:, ii] = np.dot(chng_basis, pos_vec_in_EV_basis) 2466 | line_tuple_neg[:, ii] = np.dot(chng_basis, neg_vec_in_EV_basis) 2467 | 2468 | EVh = self.MT.get_eigvecs()[0] 2469 | EVn = self.MT.get_eigvecs()[1] 2470 | EVs = self.MT.get_eigvecs()[2] 2471 | 2472 | all_EV = np.zeros((3, 6)) 2473 | 2474 | all_EV[:, 0] = EVh.transpose() 2475 | all_EV[:, 1] = EVn.transpose() 2476 | all_EV[:, 2] = EVs.transpose() 2477 | all_EV[:, 3] = -EVh.transpose() 2478 | all_EV[:, 4] = -EVn.transpose() 2479 | all_EV[:, 5] = -EVs.transpose() 2480 | 2481 | # basis vectors: 2482 | all_BV = np.zeros((3, 6)) 2483 | all_BV[:, 0] = np.array((1, 0, 0)) 2484 | all_BV[:, 1] = np.array((-1, 0, 0)) 2485 | all_BV[:, 2] = np.array((0, 1, 0)) 2486 | all_BV[:, 3] = np.array((0, -1, 0)) 2487 | all_BV[:, 4] = np.array((0, 0, 1)) 2488 | all_BV[:, 5] = np.array((0, 0, -1)) 2489 | 2490 | # re-sort the two 90 degree nodal lines to 2 fault planes - cut each at 2491 | # halves and merge pairs 2492 | # additionally change basis system to NED reference system 2493 | 2494 | midpoint_idx = int(n_curve_points / 2.) 2495 | 2496 | FP1 = np.zeros((3, n_curve_points)) 2497 | FP2 = np.zeros((3, n_curve_points)) 2498 | 2499 | for ii in xrange(midpoint_idx): 2500 | FP1_vec = np.array([H_values_FP[ii], N_values_FP[ii], 2501 | S_values_positive_FP[ii]]).transpose() 2502 | FP2_vec = np.array([H_values_FP[ii], N_values_FP[ii], 2503 | S_values_negative_FP[ii]]).transpose() 2504 | FP1[:, ii] = np.dot(chng_basis, FP1_vec) 2505 | FP2[:, ii] = np.dot(chng_basis, FP2_vec) 2506 | 2507 | for jj in xrange(midpoint_idx): 2508 | ii = n_curve_points - jj - 1 2509 | 2510 | FP1_vec = np.array([H_values_FP[ii], N_values_FP[ii], 2511 | S_values_negative_FP[ii]]).transpose() 2512 | FP2_vec = np.array([H_values_FP[ii], N_values_FP[ii], 2513 | S_values_positive_FP[ii]]).transpose() 2514 | FP1[:, ii] = np.dot(chng_basis, FP1_vec) 2515 | FP2[:, ii] = np.dot(chng_basis, FP2_vec) 2516 | 2517 | # identify with faultplane index, gotten from 'get_fps': 2518 | self._FP1 = FP1 2519 | self._FP2 = FP2 2520 | 2521 | self._all_EV = all_EV 2522 | self._all_BV = all_BV 2523 | self._nodalline_negative = line_tuple_neg 2524 | self._nodalline_positive = line_tuple_pos 2525 | 2526 | def _identify_faultplanes(self): 2527 | """ 2528 | See, if the 2 faultplanes, given as attribute of the moment 2529 | tensor object, handed to this instance, are consistent with 2530 | the faultplane lines, obtained from the basis solution. If 2531 | not, interchange the indices of the newly found ones. 2532 | """ 2533 | # TODO !!!!!! 2534 | pass 2535 | 2536 | def _find_basis_change_2_new_viewpoint(self): 2537 | """ 2538 | Finding the Eulerian angles, if you want to rotate an object. 2539 | 2540 | Your original view point is the position (0,0,0). Input are the 2541 | coordinates of the new point of view, equivalent to geographical 2542 | coordinates. 2543 | 2544 | Example: 2545 | 2546 | Original view onto the Earth is from right above lat=0, lon=0 with 2547 | north=upper edge, south=lower edge. Now you want to see the Earth 2548 | from a position somewhere near Baku. So lat=45, 2549 | lon=45, azimuth=0. 2550 | 2551 | The Earth must be rotated around some axis, not to be determined. 2552 | The rotation matrixx is the matrix for the change of basis to the 2553 | new local orthonormal system. 2554 | 2555 | input: 2556 | - latitude in degrees from -90 (south) to 90 (north) 2557 | - longitude in degrees from -180 (west) to 180 (east) 2558 | - azimuth in degrees from 0 (heading north) to 360 (north again) 2559 | """ 2560 | 2561 | new_latitude = self._plot_viewpoint[0] 2562 | new_longitude = self._plot_viewpoint[1] 2563 | new_azimuth = self._plot_viewpoint[2] 2564 | 2565 | s_lat = np.sin(new_latitude / rad2deg) 2566 | if abs(s_lat) < epsilon: 2567 | s_lat = 0 2568 | c_lat = np.cos(new_latitude / rad2deg) 2569 | if abs(c_lat) < epsilon: 2570 | c_lat = 0 2571 | s_lon = np.sin(new_longitude / rad2deg) 2572 | if abs(s_lon) < epsilon: 2573 | s_lon = 0 2574 | c_lon = np.cos(new_longitude / rad2deg) 2575 | if abs(c_lon) < epsilon: 2576 | c_lon = 0 2577 | 2578 | # assume input basis as NED!!! 2579 | 2580 | # original point of view therein is (0,0,-1) 2581 | # new point at lat=latitude, lon=longitude, az=0, given in old 2582 | # NED-coordinates: 2583 | # (cos(latitude), sin(latitude)*sin(longitude), 2584 | # sin(latitude)*cos(longitude) ) 2585 | # 2586 | # new " down' " is given by the negative position vector, so pointing 2587 | # inwards to the centre point 2588 | # down_prime = - ( np.array( ( s_lat, c_lat*c_lon, -c_lat*s_lon ) ) ) 2589 | down_prime = -(np.array((s_lat, c_lat * s_lon, -c_lat * c_lon))) 2590 | 2591 | #normalise: 2592 | down_prime /= np.sqrt(np.dot(down_prime, down_prime)) 2593 | 2594 | # get second local basis vector " north' " by orthogonalising 2595 | # (Gram-Schmidt method) the original north w.r.t. the new " down' " 2596 | north_prime_not_normalised = np.array((1., 0., 0.)) - \ 2597 | (np.dot(down_prime, np.array((1., 0., 0.))) / 2598 | (np.dot(down_prime, down_prime)) * down_prime) 2599 | 2600 | len_north_prime_not_normalised = \ 2601 | np.sqrt(np.dot(north_prime_not_normalised, 2602 | north_prime_not_normalised)) 2603 | # check for poles: 2604 | if np.abs(len_north_prime_not_normalised) < epsilon: 2605 | # case: north pole 2606 | if s_lat > 0: 2607 | north_prime = np.array((0., 0., 1.)) 2608 | # case: south pole 2609 | else: 2610 | north_prime = np.array((0., 0., -1.)) 2611 | else: 2612 | north_prime = \ 2613 | north_prime_not_normalised / len_north_prime_not_normalised 2614 | 2615 | # third basis vector is obtained by a cross product of the first two 2616 | east_prime = np.cross(down_prime, north_prime) 2617 | 2618 | # normalise: 2619 | east_prime /= np.sqrt(np.dot(east_prime, east_prime)) 2620 | 2621 | rotmat_pos_raw = np.zeros((3, 3)) 2622 | rotmat_pos_raw[:, 0] = north_prime 2623 | rotmat_pos_raw[:, 1] = east_prime 2624 | rotmat_pos_raw[:, 2] = down_prime 2625 | 2626 | rotmat_pos = np.asmatrix(rotmat_pos_raw).T 2627 | # this matrix gives the coordinates of a given point in the old 2628 | # coordinates w.r.t. the new system 2629 | 2630 | # up to here, only the position has changed, the angle of view 2631 | # (azimuth) has to be added by an additional rotation around the 2632 | # down'-axis (in the frame of the new coordinates) 2633 | # set up the local rotation around the new down'-axis by the given 2634 | # angle 'azimuth'. Positive values turn view counterclockwise from the 2635 | # new north' 2636 | only_rotation = np.zeros((3, 3)) 2637 | s_az = np.sin(new_azimuth / rad2deg) 2638 | if abs(s_az) < epsilon: 2639 | s_az = 0. 2640 | c_az = np.cos(new_azimuth / rad2deg) 2641 | if abs(c_az) < epsilon: 2642 | c_az = 0. 2643 | 2644 | only_rotation[2, 2] = 1 2645 | only_rotation[0, 0] = c_az 2646 | only_rotation[1, 1] = c_az 2647 | only_rotation[0, 1] = -s_az 2648 | only_rotation[1, 0] = s_az 2649 | 2650 | local_rotation = np.asmatrix(only_rotation) 2651 | 2652 | # apply rotation from left!! 2653 | total_rotation_matrix = np.dot(local_rotation, rotmat_pos) 2654 | 2655 | # yields the complete matrix for representing the old coordinates in 2656 | # the new (rotated) frame: 2657 | self._plot_basis_change = total_rotation_matrix 2658 | 2659 | def _rotate_all_objects_2_new_view(self): 2660 | """ 2661 | Rotate all relevant parts of the solution - namely the 2662 | eigenvector-projections, the 2 nodallines, and the faultplanes 2663 | - so that they are seen from the new viewpoint. 2664 | """ 2665 | objects_2_rotate = ['all_EV', 'all_BV', 'nodalline_negative', 2666 | 'nodalline_positive', 'FP1', 'FP2'] 2667 | 2668 | for obj in objects_2_rotate: 2669 | object2rotate = getattr(self, '_' + obj).transpose() 2670 | 2671 | rotated_thing = object2rotate.copy() 2672 | for i in xrange(len(object2rotate)): 2673 | rotated_thing[i] = np.dot(self._plot_basis_change, 2674 | object2rotate[i]) 2675 | 2676 | rotated_object = rotated_thing.copy() 2677 | setattr(self, '_' + obj + '_rotated', rotated_object.transpose()) 2678 | 2679 | def _vertical_2D_projection(self): 2680 | """ 2681 | Start the vertical projection of the 3D beachball onto the 2D plane. 2682 | The projection is chosen according to the attribute '_plot_projection' 2683 | """ 2684 | list_of_possible_projections = ['stereo', 'ortho', 'lambert', 'gnom'] 2685 | 2686 | if not self._plot_projection in list_of_possible_projections: 2687 | print 'desired projection not possible - choose from:\n ', 2688 | print list_of_possible_projections 2689 | raise MTError(' !! ') 2690 | 2691 | if self._plot_projection == 'stereo': 2692 | if not self._stereo_vertical(): 2693 | print 'ERROR in stereo_vertical' 2694 | raise MTError(' !! ') 2695 | elif self._plot_projection == 'ortho': 2696 | if not self._orthographic_vertical(): 2697 | print 'ERROR in stereo_vertical' 2698 | raise MTError(' !! ') 2699 | elif self._plot_projection == 'lambert': 2700 | if not self._lambert_vertical(): 2701 | print 'ERROR in stereo_vertical' 2702 | raise MTError(' !! ') 2703 | elif self._plot_projection == 'gnom': 2704 | if not self._gnomonic_vertical(): 2705 | print 'ERROR in stereo_vertical' 2706 | raise MTError(' !! ') 2707 | 2708 | def _stereo_vertical(self): 2709 | """ 2710 | Stereographic/azimuthal conformal 2D projection onto a plane, tangent 2711 | to the lowest point (0,0,1). 2712 | 2713 | Keeps the angles constant! 2714 | 2715 | The parts in the lower hemisphere are projected to the unit 2716 | sphere, the upper half to an annular region between radii r=1 2717 | and r=2. If the attribute '_show_upper_hemis' is set, the 2718 | projection is reversed. 2719 | """ 2720 | objects_2_project = ['all_EV', 'all_BV', 'nodalline_negative', 2721 | 'nodalline_positive', 'FP1', 'FP2'] 2722 | 2723 | available_coord_systems = ['NED'] 2724 | 2725 | if not self._plot_basis in available_coord_systems: 2726 | print 'desired plotting projection not possible - choose from :\n', 2727 | print available_coord_systems 2728 | raise MTError(' !! ') 2729 | 2730 | plot_upper_hem = self._plot_show_upper_hemis 2731 | 2732 | for obj in objects_2_project: 2733 | obj_name = '_' + obj + '_rotated' 2734 | o2proj = getattr(self, obj_name) 2735 | coords = o2proj.copy() 2736 | 2737 | n_points = len(o2proj[0, :]) 2738 | stereo_coords = np.zeros((2, n_points)) 2739 | 2740 | for ll in xrange(n_points): 2741 | # second component is EAST 2742 | co_x = coords[1, ll] 2743 | # first component is NORTH 2744 | co_y = coords[0, ll] 2745 | # z given in DOWN 2746 | co_z = -coords[2, ll] 2747 | 2748 | rho_hor = np.sqrt(co_x ** 2 + co_y ** 2) 2749 | 2750 | if rho_hor == 0: 2751 | new_y = 0 2752 | new_x = 0 2753 | if plot_upper_hem: 2754 | if co_z < 0: 2755 | new_x = 2 2756 | else: 2757 | if co_z > 0: 2758 | new_x = 2 2759 | else: 2760 | if co_z < 0: 2761 | new_rho = rho_hor / (1. - co_z) 2762 | if plot_upper_hem: 2763 | new_rho = 2 - (rho_hor / (1. - co_z)) 2764 | 2765 | new_x = co_x / rho_hor * new_rho 2766 | new_y = co_y / rho_hor * new_rho 2767 | else: 2768 | new_rho = 2 - (rho_hor / (1. + co_z)) 2769 | if plot_upper_hem: 2770 | new_rho = rho_hor / (1. + co_z) 2771 | 2772 | new_x = co_x / rho_hor * new_rho 2773 | new_y = co_y / rho_hor * new_rho 2774 | 2775 | stereo_coords[0, ll] = new_x 2776 | stereo_coords[1, ll] = new_y 2777 | 2778 | setattr(self, '_' + obj + '_2D', stereo_coords) 2779 | setattr(self, '_' + obj + '_final', stereo_coords) 2780 | 2781 | return 1 2782 | 2783 | def _orthographic_vertical(self): 2784 | """ 2785 | Orthographic 2D projection onto a plane, tangent to the lowest 2786 | point (0,0,1). 2787 | 2788 | Shows the natural view on a 2D sphere from large distances (assuming 2789 | parallel projection) 2790 | 2791 | The parts in the lower hemisphere are projected to the unit 2792 | sphere, the upper half to an annular region between radii r=1 2793 | and r=2. If the attribute '_show_upper_hemis' is set, the 2794 | projection is reversed. 2795 | """ 2796 | 2797 | objects_2_project = ['all_EV', 'all_BV', 'nodalline_negative', 2798 | 'nodalline_positive', 'FP1', 'FP2'] 2799 | 2800 | available_coord_systems = ['NED'] 2801 | 2802 | if not self._plot_basis in available_coord_systems: 2803 | print 'desired plotting projection not possible - choose from :\n', 2804 | print available_coord_systems 2805 | raise MTError(' !! ') 2806 | 2807 | plot_upper_hem = self._plot_show_upper_hemis 2808 | 2809 | for obj in objects_2_project: 2810 | obj_name = '_' + obj + '_rotated' 2811 | o2proj = getattr(self, obj_name) 2812 | coords = o2proj.copy() 2813 | 2814 | n_points = len(o2proj[0, :]) 2815 | coords2D = np.zeros((2, n_points)) 2816 | 2817 | for ll in xrange(n_points): 2818 | # second component is EAST 2819 | co_x = coords[1, ll] 2820 | # first component is NORTH 2821 | co_y = coords[0, ll] 2822 | # z given in DOWN 2823 | co_z = -coords[2, ll] 2824 | 2825 | rho_hor = np.sqrt(co_x ** 2 + co_y ** 2) 2826 | 2827 | if rho_hor == 0: 2828 | new_y = 0 2829 | new_x = 0 2830 | if plot_upper_hem: 2831 | if co_z < 0: 2832 | new_x = 2 2833 | else: 2834 | if co_z > 0: 2835 | new_x = 2 2836 | else: 2837 | if co_z < 0: 2838 | new_rho = rho_hor 2839 | if plot_upper_hem: 2840 | new_rho = 2 - rho_hor 2841 | 2842 | new_x = co_x / rho_hor * new_rho 2843 | new_y = co_y / rho_hor * new_rho 2844 | else: 2845 | new_rho = 2 - rho_hor 2846 | if plot_upper_hem: 2847 | new_rho = rho_hor 2848 | 2849 | new_x = co_x / rho_hor * new_rho 2850 | new_y = co_y / rho_hor * new_rho 2851 | 2852 | coords2D[0, ll] = new_x 2853 | coords2D[1, ll] = new_y 2854 | 2855 | setattr(self, '_' + obj + '_2D', coords2D) 2856 | setattr(self, '_' + obj + '_final', coords2D) 2857 | 2858 | return 1 2859 | 2860 | def _lambert_vertical(self): 2861 | """ 2862 | Lambert azimuthal equal-area 2D projection onto a plane, tangent to the 2863 | lowest point (0,0,1). 2864 | 2865 | Keeps the area constant! 2866 | 2867 | The parts in the lower hemisphere are projected to the unit 2868 | sphere (only here the area is kept constant), the upper half to an 2869 | annular region between radii r=1 and r=2. If the attribute 2870 | '_show_upper_hemis' is set, the projection is reversed. 2871 | """ 2872 | objects_2_project = ['all_EV', 'all_BV', 'nodalline_negative', 2873 | 'nodalline_positive', 'FP1', 'FP2'] 2874 | 2875 | available_coord_systems = ['NED'] 2876 | 2877 | if not self._plot_basis in available_coord_systems: 2878 | print 'desired plotting projection not possible - choose from :\n', 2879 | print available_coord_systems 2880 | raise MTError(' !! ') 2881 | 2882 | plot_upper_hem = self._plot_show_upper_hemis 2883 | 2884 | for obj in objects_2_project: 2885 | obj_name = '_' + obj + '_rotated' 2886 | o2proj = getattr(self, obj_name) 2887 | coords = o2proj.copy() 2888 | 2889 | n_points = len(o2proj[0, :]) 2890 | coords2D = np.zeros((2, n_points)) 2891 | 2892 | for ll in xrange(n_points): 2893 | # second component is EAST 2894 | co_x = coords[1, ll] 2895 | # first component is NORTH 2896 | co_y = coords[0, ll] 2897 | # z given in DOWN 2898 | co_z = -coords[2, ll] 2899 | 2900 | rho_hor = np.sqrt(co_x ** 2 + co_y ** 2) 2901 | 2902 | if rho_hor == 0: 2903 | new_y = 0 2904 | new_x = 0 2905 | if plot_upper_hem: 2906 | if co_z < 0: 2907 | new_x = 2 2908 | else: 2909 | if co_z > 0: 2910 | new_x = 2 2911 | else: 2912 | if co_z < 0: 2913 | new_rho = rho_hor / np.sqrt(1. - co_z) 2914 | 2915 | if plot_upper_hem: 2916 | new_rho = 2 - (rho_hor / np.sqrt(1. - co_z)) 2917 | 2918 | new_x = co_x / rho_hor * new_rho 2919 | new_y = co_y / rho_hor * new_rho 2920 | 2921 | else: 2922 | new_rho = 2 - (rho_hor / np.sqrt(1. + co_z)) 2923 | 2924 | if plot_upper_hem: 2925 | new_rho = rho_hor / np.sqrt(1. + co_z) 2926 | 2927 | new_x = co_x / rho_hor * new_rho 2928 | new_y = co_y / rho_hor * new_rho 2929 | 2930 | coords2D[0, ll] = new_x 2931 | coords2D[1, ll] = new_y 2932 | 2933 | setattr(self, '_' + obj + '_2D', coords2D) 2934 | setattr(self, '_' + obj + '_final', coords2D) 2935 | 2936 | return 1 2937 | 2938 | def _gnomonic_vertical(self): 2939 | """ 2940 | Gnomonic 2D projection onto a plane, tangent to the lowest 2941 | point (0,0,1). 2942 | 2943 | Keeps the great circles as straight lines (geodetics constant) ! 2944 | 2945 | The parts in the lower hemisphere are projected to the unit 2946 | sphere, the upper half to an annular region between radii r=1 2947 | and r=2. If the attribute '_show_upper_hemis' is set, the 2948 | projection is reversed. 2949 | """ 2950 | 2951 | objects_2_project = ['all_EV', 'all_BV', 'nodalline_negative', 2952 | 'nodalline_positive', 'FP1', 'FP2'] 2953 | 2954 | available_coord_systems = ['NED'] 2955 | 2956 | if not self._plot_basis in available_coord_systems: 2957 | print 'desired plotting projection not possible - choose from :\n', 2958 | print available_coord_systems 2959 | raise MTError(' !! ') 2960 | 2961 | plot_upper_hem = self._plot_show_upper_hemis 2962 | 2963 | for obj in objects_2_project: 2964 | obj_name = '_' + obj + '_rotated' 2965 | o2proj = getattr(self, obj_name) 2966 | coords = o2proj.copy() 2967 | 2968 | n_points = len(o2proj[0, :]) 2969 | coords2D = np.zeros((2, n_points)) 2970 | 2971 | for ll in xrange(n_points): 2972 | # second component is EAST 2973 | co_x = coords[1, ll] 2974 | # first component is NORTH 2975 | co_y = coords[0, ll] 2976 | # z given in DOWN 2977 | co_z = -coords[2, ll] 2978 | 2979 | rho_hor = np.sqrt(co_x ** 2 + co_y ** 2) 2980 | 2981 | if rho_hor == 0: 2982 | new_y = 0 2983 | new_x = 0 2984 | if co_z > 0: 2985 | new_x = 2 2986 | if plot_upper_hem: 2987 | new_x = 0 2988 | else: 2989 | if co_z < 0: 2990 | new_rho = np.cos(np.arcsin(rho_hor)) * \ 2991 | np.tan(np.arcsin(rho_hor)) 2992 | 2993 | if plot_upper_hem: 2994 | new_rho = 2 - (np.cos(np.arcsin(rho_hor)) * 2995 | np.tan(np.arcsin(rho_hor))) 2996 | 2997 | new_x = co_x / rho_hor * new_rho 2998 | new_y = co_y / rho_hor * new_rho 2999 | 3000 | else: 3001 | new_rho = 2 - (np.cos(np.arcsin(rho_hor)) * 3002 | np.tan(np.arcsin(rho_hor))) 3003 | 3004 | if plot_upper_hem: 3005 | new_rho = np.cos(np.arcsin(rho_hor)) * \ 3006 | np.tan(np.arcsin(rho_hor)) 3007 | 3008 | new_x = co_x / rho_hor * new_rho 3009 | new_y = co_y / rho_hor * new_rho 3010 | 3011 | coords2D[0, ll] = new_x 3012 | coords2D[1, ll] = new_y 3013 | 3014 | setattr(self, '_' + obj + '_2D', coords2D) 3015 | setattr(self, '_' + obj + '_final', coords2D) 3016 | 3017 | return 1 3018 | 3019 | def _build_circles(self): 3020 | """ 3021 | Sets two sets of points, describing the unit sphere and the outer 3022 | circle with r=2. 3023 | 3024 | Added as attributes '_unit_sphere' and '_outer_circle'. 3025 | """ 3026 | phi = self._phi_curve 3027 | 3028 | UnitSphere = np.zeros((2, len(phi))) 3029 | UnitSphere[0, :] = np.cos(phi) 3030 | UnitSphere[1, :] = np.sin(phi) 3031 | 3032 | # outer circle ( radius for stereographic projection is set to 2 ) 3033 | outer_circle_points = 2 * UnitSphere 3034 | 3035 | self._unit_sphere = UnitSphere 3036 | self._outer_circle = outer_circle_points 3037 | 3038 | def _sort_curve_points(self, curve): 3039 | """ 3040 | Checks, if curve points are in right order for line plotting. 3041 | 3042 | If not, a re-arranging is carried out. 3043 | """ 3044 | sorted_curve = np.zeros((2, len(curve[0, :]))) 3045 | # in polar coordinates 3046 | r_phi_curve = np.zeros((len(curve[0, :]), 2)) 3047 | for ii in xrange(curve.shape[1]): 3048 | r_phi_curve[ii, 0] = \ 3049 | math.sqrt(curve[0, ii] ** 2 + curve[1, ii] ** 2) 3050 | r_phi_curve[ii, 1] = \ 3051 | math.atan2(curve[0, ii], curve[1, ii]) % (2 * pi) 3052 | # find index with highest r 3053 | largest_r_idx = np.argmax(r_phi_curve[:, 0]) 3054 | 3055 | # check, if perhaps more values with same r - if so, take point with 3056 | # lowest phi 3057 | other_idces = \ 3058 | list(np.where(r_phi_curve[:, 0] == r_phi_curve[largest_r_idx, 0])) 3059 | if len(other_idces) > 1: 3060 | best_idx = np.argmin(r_phi_curve[other_idces, 1]) 3061 | start_idx_curve = other_idces[best_idx] 3062 | else: 3063 | start_idx_curve = largest_r_idx 3064 | 3065 | if not start_idx_curve == 0: 3066 | pass 3067 | 3068 | # check orientation - want to go inwards 3069 | start_r = r_phi_curve[start_idx_curve, 0] 3070 | next_idx = (start_idx_curve + 1) % len(r_phi_curve[:, 0]) 3071 | prep_idx = (start_idx_curve - 1) % len(r_phi_curve[:, 0]) 3072 | next_r = r_phi_curve[next_idx, 0] 3073 | 3074 | keep_direction = True 3075 | if next_r <= start_r: 3076 | # check, if next R is on other side of area - look at total 3077 | # distance - if yes, reverse direction 3078 | dist_first_next = \ 3079 | (curve[0, next_idx] - curve[0, start_idx_curve]) ** 2 + \ 3080 | (curve[1, next_idx] - curve[1, start_idx_curve]) ** 2 3081 | dist_first_other = \ 3082 | (curve[0, prep_idx] - curve[0, start_idx_curve]) ** 2 + \ 3083 | (curve[1, prep_idx] - curve[1, start_idx_curve]) ** 2 3084 | 3085 | if dist_first_next > dist_first_other: 3086 | keep_direction = False 3087 | 3088 | if keep_direction: 3089 | # direction is kept 3090 | for jj in xrange(curve.shape[1]): 3091 | running_idx = (start_idx_curve + jj) % len(curve[0, :]) 3092 | sorted_curve[0, jj] = curve[0, running_idx] 3093 | sorted_curve[1, jj] = curve[1, running_idx] 3094 | else: 3095 | # direction is reversed 3096 | for jj in xrange(curve.shape[1]): 3097 | running_idx = (start_idx_curve - jj) % len(curve[0, :]) 3098 | sorted_curve[0, jj] = curve[0, running_idx] 3099 | sorted_curve[1, jj] = curve[1, running_idx] 3100 | 3101 | # check if step of first to second point does not have large angle 3102 | # step (problem caused by projection of point (pole) onto whole 3103 | # edge - if this first angle step is larger than the one between 3104 | # points 2 and three, correct position of first point: keep R, but 3105 | # take angle with same difference as point 2 to point 3 3106 | 3107 | angle_point_1 = (math.atan2(sorted_curve[0, 0], 3108 | sorted_curve[1, 0]) % (2 * pi)) 3109 | angle_point_2 = (math.atan2(sorted_curve[0, 1], 3110 | sorted_curve[1, 1]) % (2 * pi)) 3111 | angle_point_3 = (math.atan2(sorted_curve[0, 2], 3112 | sorted_curve[1, 2]) % (2 * pi)) 3113 | 3114 | angle_diff_23 = (angle_point_3 - angle_point_2) 3115 | if angle_diff_23 > pi: 3116 | angle_diff_23 = (-angle_diff_23) % (2 * pi) 3117 | 3118 | angle_diff_12 = (angle_point_2 - angle_point_1) 3119 | if angle_diff_12 > pi: 3120 | angle_diff_12 = (-angle_diff_12) % (2 * pi) 3121 | 3122 | if abs(angle_diff_12) > abs(angle_diff_23): 3123 | r_old = \ 3124 | math.sqrt(sorted_curve[0, 0] ** 2 + sorted_curve[1, 0] ** 2) 3125 | new_angle = (angle_point_2 - angle_diff_23) % (2 * pi) 3126 | sorted_curve[0, 0] = r_old * math.sin(new_angle) 3127 | sorted_curve[1, 0] = r_old * math.cos(new_angle) 3128 | 3129 | return sorted_curve 3130 | 3131 | def _smooth_curves(self): 3132 | """ 3133 | Corrects curves for potential large gaps, resulting in strange 3134 | intersection lines on nodals of round and irreagularly shaped 3135 | areas. 3136 | 3137 | At least one coordinte point on each degree on the circle is assured. 3138 | """ 3139 | list_of_curves_2_smooth = ['nodalline_negative', 'nodalline_positive', 3140 | 'FP1', 'FP2'] 3141 | 3142 | points_per_degree = self._plot_n_points / 360. 3143 | 3144 | for curve2smooth in list_of_curves_2_smooth: 3145 | obj_name = curve2smooth + '_in_order' 3146 | obj = getattr(self, '_' + obj_name).transpose() 3147 | 3148 | smoothed_array = np.zeros((1, 2)) 3149 | smoothed_array[0, :] = obj[0] 3150 | smoothed_list = [smoothed_array] 3151 | 3152 | # now in shape (n_points,2) 3153 | for idx, val in enumerate(obj[:-1]): 3154 | r1 = math.sqrt(val[0] ** 2 + val[1] ** 2) 3155 | r2 = math.sqrt(obj[idx + 1][0] ** 2 + obj[idx + 1][1] ** 2) 3156 | phi1 = math.atan2(val[0], val[1]) 3157 | phi2 = math.atan2(obj[idx + 1][0], obj[idx + 1][1]) 3158 | 3159 | phi2_larger = np.sign(phi2 - phi1) 3160 | angle_smaller_pi = np.sign(pi - abs(phi2 - phi1)) 3161 | 3162 | if phi2_larger * angle_smaller_pi > 0: 3163 | go_cw = True 3164 | openangle = (phi2 - phi1) % (2 * pi) 3165 | else: 3166 | go_cw = False 3167 | openangle = (phi1 - phi2) % (2 * pi) 3168 | 3169 | openangle_deg = openangle * rad2deg 3170 | radius_diff = r2 - r1 3171 | 3172 | if openangle_deg > 1. / points_per_degree: 3173 | 3174 | n_fillpoints = int(openangle_deg * points_per_degree) 3175 | fill_array = np.zeros((n_fillpoints, 2)) 3176 | if go_cw: 3177 | angles = ((np.arange(n_fillpoints) + 1) * openangle / 3178 | (n_fillpoints + 1) + phi1) % (2 * pi) 3179 | else: 3180 | angles = (phi1 - (np.arange(n_fillpoints) + 1) * 3181 | openangle / (n_fillpoints + 1)) % (2 * pi) 3182 | 3183 | radii = (np.arange(n_fillpoints) + 1) * \ 3184 | radius_diff / (n_fillpoints + 1) + r1 3185 | 3186 | fill_array[:, 0] = radii * np.sin(angles) 3187 | fill_array[:, 1] = radii * np.cos(angles) 3188 | 3189 | smoothed_list.append(fill_array) 3190 | 3191 | smoothed_list.append([obj[idx + 1]]) 3192 | 3193 | smoothed_array = np.vstack(smoothed_list) 3194 | setattr(self, '_' + curve2smooth + '_final', 3195 | smoothed_array.transpose()) 3196 | 3197 | def _check_curve_in_curve(self): 3198 | """ 3199 | Checks, if one of the two nodallines contains the other one 3200 | completely. If so, the order of colours is re-adapted, 3201 | assuring the correct order when doing the overlay plotting. 3202 | """ 3203 | lo_points_in_pos_curve = \ 3204 | list(self._nodalline_positive_final.transpose()) 3205 | lo_points_in_pos_curve_array = \ 3206 | self._nodalline_positive_final.transpose() 3207 | lo_points_in_neg_curve = \ 3208 | list(self._nodalline_negative_final.transpose()) 3209 | lo_points_in_neg_curve_array = \ 3210 | self._nodalline_negative_final.transpose() 3211 | 3212 | # check, if negative curve completely within positive curve 3213 | mask_neg_in_pos = 0 3214 | for neg_point in lo_points_in_neg_curve: 3215 | mask_neg_in_pos += self._pnpoly(lo_points_in_pos_curve_array, 3216 | neg_point[:2]) 3217 | if mask_neg_in_pos > len(lo_points_in_neg_curve) - 3: 3218 | self._plot_curve_in_curve = 1 3219 | 3220 | # check, if positive curve completely within negative curve 3221 | mask_pos_in_neg = 0 3222 | for pos_point in lo_points_in_pos_curve: 3223 | mask_pos_in_neg += self._pnpoly(lo_points_in_neg_curve_array, 3224 | pos_point[:2]) 3225 | if mask_pos_in_neg > len(lo_points_in_pos_curve) - 3: 3226 | self._plot_curve_in_curve = -1 3227 | 3228 | # correct for ONE special case: double couple with its 3229 | # eigensystem = NED basis system: 3230 | testarray = [1., 0, 0, 0, 1, 0, 0, 0, 1] 3231 | if np.prod(self.MT._rotation_matrix.A1 == testarray) and \ 3232 | (self.MT._eigenvalues[1] == 0): 3233 | self._plot_curve_in_curve = -1 3234 | self._plot_clr_order = 1 3235 | 3236 | def _point_inside_polygon(self, x, y, poly): 3237 | """ 3238 | Determine if a point is inside a given polygon or not. 3239 | 3240 | Polygon is a list of (x,y) pairs. 3241 | """ 3242 | n = len(poly) 3243 | inside = False 3244 | 3245 | p1x, p1y = poly[0] 3246 | for i in range(n + 1): 3247 | p2x, p2y = poly[i % n] 3248 | if y > min(p1y, p2y): 3249 | if y <= max(p1y, p2y): 3250 | if x <= max(p1x, p2x): 3251 | if p1y != p2y: 3252 | xinters = \ 3253 | (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x 3254 | if p1x == p2x or x <= xinters: 3255 | inside = not inside 3256 | p1x, p1y = p2x, p2y 3257 | 3258 | return inside 3259 | 3260 | def _pnpoly(self, verts, point): 3261 | """ 3262 | Check whether point is in the polygon defined by verts. 3263 | 3264 | verts - 2xN array 3265 | point - (2,) array 3266 | 3267 | See 3268 | http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html 3269 | """ 3270 | # using take instead of getitem, about ten times faster, see 3271 | # http://wesmckinney.com/blog/?p=215 3272 | verts = np.require(verts, dtype=np.float64) 3273 | x, y = point 3274 | 3275 | xpi = verts[:, 0] 3276 | ypi = verts[:, 1] 3277 | # shift 3278 | xpj = xpi.take(self.arange_1[:xpi.size]) 3279 | ypj = ypi.take(self.arange_1[:ypi.size]) 3280 | 3281 | possible_crossings = \ 3282 | ((ypi <= y) & (y < ypj)) | ((ypj <= y) & (y < ypi)) 3283 | 3284 | xpi = xpi[possible_crossings] 3285 | ypi = ypi[possible_crossings] 3286 | xpj = xpj[possible_crossings] 3287 | ypj = ypj[possible_crossings] 3288 | 3289 | crossings = x < (xpj - xpi) * (y - ypi) / (ypj - ypi) + xpi 3290 | 3291 | return crossings.sum() % 2 3292 | 3293 | def _projection_2_unit_sphere(self): 3294 | """ 3295 | Brings the complete solution (from stereographic projection) 3296 | onto the unit sphere by just shrinking the maximum radius of 3297 | all points to 1. 3298 | 3299 | This keeps the area definitions, so the colouring is not affected. 3300 | """ 3301 | list_of_objects_2_project = ['nodalline_positive_final', 3302 | 'nodalline_negative_final'] 3303 | lo_fps = ['FP1_final', 'FP2_final'] 3304 | 3305 | for obj2proj in list_of_objects_2_project: 3306 | obj = getattr(self, '_' + obj2proj).transpose().copy() 3307 | for idx, val in enumerate(obj): 3308 | old_radius = np.sqrt(val[0] ** 2 + val[1] ** 2) 3309 | if old_radius > 1: 3310 | obj[idx, 0] = val[0] / old_radius 3311 | obj[idx, 1] = val[1] / old_radius 3312 | 3313 | setattr(self, '_' + obj2proj + '_US', obj.transpose()) 3314 | 3315 | for fp in lo_fps: 3316 | obj = getattr(self, '_' + fp).transpose().copy() 3317 | 3318 | tmp_obj = [] 3319 | for idx, val in enumerate(obj): 3320 | old_radius = np.sqrt(val[0] ** 2 + val[1] ** 2) 3321 | if old_radius <= 1 + epsilon: 3322 | tmp_obj.append(val) 3323 | tmp_obj2 = np.array(tmp_obj).transpose() 3324 | tmp_obj3 = self._sort_curve_points(tmp_obj2) 3325 | 3326 | setattr(self, '_' + fp + '_US', tmp_obj3) 3327 | 3328 | lo_visible_EV = [] 3329 | 3330 | for idx, val in enumerate(self._all_EV_2D.transpose()): 3331 | r_ev = np.sqrt(val[0] ** 2 + val[1] ** 2) 3332 | if r_ev <= 1: 3333 | lo_visible_EV.append([val[0], val[1], idx]) 3334 | visible_EVs = np.array(lo_visible_EV) 3335 | 3336 | self._all_EV_2D_US = visible_EVs 3337 | 3338 | lo_visible_BV = [] 3339 | dummy_list1 = [] 3340 | direction_letters = list('NSEWDU') 3341 | 3342 | for idx, val in enumerate(self._all_BV_2D.transpose()): 3343 | r_bv = math.sqrt(val[0] ** 2 + val[1] ** 2) 3344 | if r_bv <= 1: 3345 | if idx == 1 and 'N' in dummy_list1: 3346 | continue 3347 | elif idx == 3 and 'E' in dummy_list1: 3348 | continue 3349 | elif idx == 5 and 'D' in dummy_list1: 3350 | continue 3351 | else: 3352 | lo_visible_BV.append([val[0], val[1], idx]) 3353 | dummy_list1.append(direction_letters[idx]) 3354 | 3355 | visible_BVs = np.array(lo_visible_BV) 3356 | 3357 | self._all_BV_2D_US = visible_BVs 3358 | 3359 | def _plot_US(self, ax=None): 3360 | """ 3361 | Generates the final plot of the beachball projection on the unit 3362 | sphere. 3363 | 3364 | Additionally, the plot can be saved in a file on the fly. 3365 | """ 3366 | import pylab as P 3367 | 3368 | plotfig = self._setup_plot_US(P, ax=ax) 3369 | 3370 | if self._plot_save_plot: 3371 | try: 3372 | plotfig.savefig(self._plot_outfile + '.' + 3373 | self._plot_outfile_format, dpi=self._plot_dpi, 3374 | transparent=True, 3375 | format=self._plot_outfile_format) 3376 | except: 3377 | print 'saving of plot not possible' 3378 | P.show() 3379 | P.close('all') 3380 | 3381 | def _setup_plot_US(self, P, ax=None): 3382 | """ 3383 | Setting up the figure with the final plot of the unit sphere. 3384 | 3385 | Either called by _plot_US or by _just_save_bb 3386 | """ 3387 | P.close(667) 3388 | if ax is None: 3389 | plotfig = P.figure(667, figsize=(self._plot_size, self._plot_size)) 3390 | plotfig.subplots_adjust(left=0, bottom=0, right=1, top=1) 3391 | ax = plotfig.add_subplot(111, aspect='equal') 3392 | 3393 | ax.axison = False 3394 | 3395 | neg_nodalline = self._nodalline_negative_final_US 3396 | pos_nodalline = self._nodalline_positive_final_US 3397 | FP1_2_plot = self._FP1_final_US 3398 | FP2_2_plot = self._FP2_final_US 3399 | 3400 | US = self._unit_sphere 3401 | 3402 | tension_colour = self._plot_tension_colour 3403 | pressure_colour = self._plot_pressure_colour 3404 | 3405 | if self._plot_fill_flag: 3406 | if self._plot_clr_order > 0: 3407 | alpha = self._plot_fill_alpha * self._plot_total_alpha 3408 | 3409 | ax.fill(US[0, :], US[1, :], fc=pressure_colour, alpha=alpha) 3410 | ax.fill(neg_nodalline[0, :], neg_nodalline[1, :], 3411 | fc=tension_colour, alpha=alpha) 3412 | ax.fill(pos_nodalline[0, :], pos_nodalline[1, :], 3413 | fc=tension_colour, alpha=alpha) 3414 | 3415 | if self._plot_curve_in_curve != 0: 3416 | ax.fill(US[0, :], US[1, :], fc=tension_colour, 3417 | alpha=alpha) 3418 | 3419 | if self._plot_curve_in_curve < 1: 3420 | ax.fill(neg_nodalline[0, :], neg_nodalline[1, :], 3421 | fc=pressure_colour, alpha=alpha) 3422 | ax.fill(pos_nodalline[0, :], pos_nodalline[1, :], 3423 | fc=tension_colour, alpha=alpha) 3424 | pass 3425 | else: 3426 | ax.fill(pos_nodalline[0, :], pos_nodalline[1, :], 3427 | fc=pressure_colour, alpha=alpha) 3428 | ax.fill(neg_nodalline[0, :], neg_nodalline[1, :], 3429 | fc=tension_colour, alpha=alpha) 3430 | pass 3431 | 3432 | EV_sym = ['m^', 'b^', 'g^', 'mv', 'bv', 'gv'] 3433 | 3434 | if self._plot_show_princ_axes: 3435 | alpha = \ 3436 | self._plot_princ_axes_alpha * self._plot_total_alpha 3437 | 3438 | for val in self._all_EV_2D_US: 3439 | ax.plot([val[0]], [val[1]], EV_sym[int(val[2])], 3440 | ms=self._plot_princ_axes_symsize, 3441 | lw=self._plot_princ_axes_lw, alpha=alpha) 3442 | 3443 | else: 3444 | alpha = self._plot_fill_alpha * self._plot_total_alpha 3445 | 3446 | ax.fill(US[0, :], US[1, :], fc=tension_colour, alpha=alpha) 3447 | ax.fill(neg_nodalline[0, :], neg_nodalline[1, :], 3448 | fc=pressure_colour, alpha=alpha) 3449 | ax.fill(pos_nodalline[0, :], pos_nodalline[1, :], 3450 | fc=pressure_colour, alpha=alpha) 3451 | 3452 | if self._plot_curve_in_curve != 0: 3453 | ax.fill(US[0, :], US[1, :], fc=pressure_colour, 3454 | alpha=alpha) 3455 | 3456 | if self._plot_curve_in_curve < 1: 3457 | ax.fill(neg_nodalline[0, :], neg_nodalline[1, :], 3458 | fc=tension_colour, alpha=alpha) 3459 | ax.fill(pos_nodalline[0, :], pos_nodalline[1, :], 3460 | fc=pressure_colour, alpha=alpha) 3461 | pass 3462 | else: 3463 | ax.fill(pos_nodalline[0, :], pos_nodalline[1, :], 3464 | fc=tension_colour, alpha=alpha) 3465 | ax.fill(neg_nodalline[0, :], neg_nodalline[1, :], 3466 | fc=pressure_colour, alpha=alpha) 3467 | pass 3468 | 3469 | EV_sym = ['g^', 'b^', 'm^', 'gv', 'bv', 'mv'] 3470 | if self._plot_show_princ_axes: 3471 | alpha = self._plot_princ_axes_alpha * self._plot_total_alpha 3472 | 3473 | for val in self._all_EV_2D_US: 3474 | ax.plot([val[0]], [val[1]], EV_sym[int(val[2])], 3475 | ms=self._plot_princ_axes_symsize, 3476 | lw=self._plot_princ_axes_lw, alpha=alpha) 3477 | 3478 | # 3479 | # set all nodallines and faultplanes for plotting: 3480 | # 3481 | 3482 | ax.plot(neg_nodalline[0, :], neg_nodalline[1, :], 3483 | c=self._plot_nodalline_colour, ls='-', 3484 | lw=self._plot_nodalline_width, 3485 | alpha=self._plot_nodalline_alpha * self._plot_total_alpha) 3486 | #ax.plot( neg_nodalline[0,:] ,neg_nodalline[1,:],'go') 3487 | 3488 | ax.plot(pos_nodalline[0, :], pos_nodalline[1, :], 3489 | c=self._plot_nodalline_colour, ls='-', 3490 | lw=self._plot_nodalline_width, 3491 | alpha=self._plot_nodalline_alpha * self._plot_total_alpha) 3492 | 3493 | if self._plot_show_faultplanes: 3494 | ax.plot(FP1_2_plot[0, :], FP1_2_plot[1, :], 3495 | c=self._plot_faultplane_colour, ls='-', 3496 | lw=self._plot_faultplane_width, 3497 | alpha=self._plot_faultplane_alpha * self._plot_total_alpha) 3498 | ax.plot(FP2_2_plot[0, :], FP2_2_plot[1, :], 3499 | c=self._plot_faultplane_colour, ls='-', 3500 | lw=self._plot_faultplane_width, 3501 | alpha=self._plot_faultplane_alpha * self._plot_total_alpha) 3502 | 3503 | elif self._plot_show_1faultplane: 3504 | if not self._plot_show_FP_index in [1, 2]: 3505 | print 'no fault plane specified for being plotted... ', 3506 | print 'continue without faultplane' 3507 | pass 3508 | else: 3509 | alpha = self._plot_faultplane_alpha * self._plot_total_alpha 3510 | if self._plot_show_FP_index == 1: 3511 | ax.plot(FP1_2_plot[0, :], FP1_2_plot[1, :], 3512 | c=self._plot_faultplane_colour, ls='-', 3513 | lw=self._plot_faultplane_width, alpha=alpha) 3514 | else: 3515 | ax.plot(FP2_2_plot[0, :], FP2_2_plot[1, :], 3516 | c=self._plot_faultplane_colour, ls='-', 3517 | lw=self._plot_faultplane_width, alpha=alpha) 3518 | 3519 | # if isotropic part shall be displayed, fill the circle completely with 3520 | # the appropriate colour 3521 | 3522 | if self._pure_isotropic: 3523 | # f abs( np.trace( self._M )) > epsilon: 3524 | if self._plot_clr_order < 0: 3525 | ax.fill(US[0, :], US[1, :], fc=tension_colour, alpha=1, 3526 | zorder=100) 3527 | else: 3528 | ax.fill(US[0, :], US[1, :], fc=pressure_colour, alpha=1, 3529 | zorder=100) 3530 | 3531 | # plot outer circle line of US 3532 | ax.plot(US[0, :], US[1, :], c=self._plot_outerline_colour, ls='-', 3533 | lw=self._plot_outerline_width, 3534 | alpha=self._plot_outerline_alpha * self._plot_total_alpha) 3535 | 3536 | # plot NED basis vectors 3537 | if self._plot_show_basis_axes: 3538 | plot_size_in_points = self._plot_size * 2.54 * 72 3539 | points_per_unit = plot_size_in_points / 2. 3540 | 3541 | fontsize = plot_size_in_points / 40. 3542 | symsize = plot_size_in_points / 61. 3543 | 3544 | direction_letters = list('NSEWDU') 3545 | 3546 | for val in self._all_BV_2D_US: 3547 | x_coord = val[0] 3548 | y_coord = val[1] 3549 | np_letter = direction_letters[int(val[2])] 3550 | 3551 | rot_angle = -np.arctan2(y_coord, x_coord) + pi / 2. 3552 | original_rho = np.sqrt(x_coord ** 2 + y_coord ** 2) 3553 | 3554 | marker_x = (original_rho - (1.5 * symsize / points_per_unit)) \ 3555 | * np.sin(rot_angle) 3556 | marker_y = (original_rho - (1.5 * symsize / points_per_unit)) \ 3557 | * np.cos(rot_angle) 3558 | annot_x = (original_rho - (4.5 * fontsize / points_per_unit)) \ 3559 | * np.sin(rot_angle) 3560 | annot_y = (original_rho - (4.5 * fontsize / points_per_unit)) \ 3561 | * np.cos(rot_angle) 3562 | 3563 | ax.text(annot_x, annot_y, np_letter, 3564 | horizontalalignment='center', size=fontsize, 3565 | weight='bold', verticalalignment='center', 3566 | bbox=dict(edgecolor='white', facecolor='white', 3567 | alpha=1)) 3568 | 3569 | if original_rho > epsilon: 3570 | ax.scatter([marker_x], [marker_y], 3571 | marker=(3, 0, rot_angle), s=symsize ** 2, 3572 | c='k', facecolor='k', zorder=300) 3573 | else: 3574 | ax.scatter([x_coord], [y_coord], marker=(4, 1, rot_angle), 3575 | s=symsize ** 2, c='k', facecolor='k', 3576 | zorder=300) 3577 | 3578 | # plot 4 fake points, guaranteeing full visibilty of the sphere 3579 | ax.plot([0, 1.05, 0, -1.05], [1.05, 0, -1.05, 0], ',', alpha=0.) 3580 | # scaling behaviour 3581 | ax.autoscale_view(tight=True, scalex=True, scaley=True) 3582 | 3583 | return plotfig 3584 | 3585 | 3586 | if __name__ == "__main__": 3587 | 3588 | from optparse import OptionParser, OptionGroup 3589 | 3590 | decomp_attrib_map_keys = ('in', 'out', 'type', 3591 | 'full', 3592 | 'iso', 'iso_perc', 3593 | 'dev', 'devi', 'devi_perc', 3594 | 'dc', 'dc_perc', 3595 | 'dc2', 'dc2_perc', 3596 | 'dc3', 'dc3_perc', 3597 | 'clvd', 'clvd_perc', 3598 | 'mom', 'mag', 3599 | 'eigvals', 'eigvecs', 3600 | 't', 'n', 'p') 3601 | 3602 | decomp_attrib_map = dict(zip(decomp_attrib_map_keys, 3603 | ('input_system', 'output_system', 3604 | 'decomp_type', 'M', 3605 | 'iso', 'iso_percentage', 3606 | 'devi', 'devi', 'devi_percentage', 3607 | 'DC', 'DC_percentage', 3608 | 'DC2', 'DC2_percentage', 3609 | 'DC3', 'DC3_percentage', 3610 | 'CLVD', 'CLVD_percentage', 3611 | 'moment', 'mag', 3612 | 'eigvals', 'eigvecs', 3613 | 't_axis', 'null_axis', 'p_axis') 3614 | )) 3615 | 3616 | lo_allowed_systems = ['NED', 'USE', 'XYZ', 'NWU'] 3617 | 3618 | def _handle_input(call, M_in, call_args, optparser): 3619 | """ 3620 | take the original method and its arguments, the source mechanism, 3621 | and the dictionary with proper parsers for each call, 3622 | """ 3623 | 3624 | # construct a dict with consistent keywordargs suited for the current 3625 | # call 3626 | kwargs = _parse_arguments(call, call_args, optparser) 3627 | 3628 | # set the fitting input basis system 3629 | in_system = kwargs.get('in_system', 'NED') 3630 | out_system = kwargs.get('out_system', 'NED') 3631 | 3632 | # build the moment tensor object 3633 | mt = MomentTensor( 3634 | M=M_in, in_system=in_system, out_system=out_system) 3635 | 3636 | # if only parts of M are to be plotted, M must be reduced to this part 3637 | # already here! 3638 | if call == 'plot' and kwargs['plot_part_of_m']: 3639 | if kwargs['plot_part_of_m'] == 'iso': 3640 | mt = MomentTensor( 3641 | M=mt.get_iso(), in_system=in_system, out_system=out_system) 3642 | 3643 | if kwargs['plot_part_of_m'] == 'devi': 3644 | mt = MomentTensor( 3645 | M=mt.get_devi(), in_system=in_system, 3646 | out_system=out_system) 3647 | 3648 | if kwargs['plot_part_of_m'] == 'dc': 3649 | mt = MomentTensor( 3650 | M=mt.get_DC(), in_system=in_system, out_system=out_system) 3651 | 3652 | if kwargs['plot_part_of_m'] == 'clvd': 3653 | mt = MomentTensor( 3654 | M=mt.get_CLVD(), in_system=in_system, 3655 | out_system=out_system) 3656 | 3657 | # call the main routine to handle the moment tensor 3658 | return _call_main(mt, call, kwargs) 3659 | 3660 | def _call_main(MT, main_call, kwargs_dict): 3661 | 3662 | if main_call == 'plot': 3663 | return _call_plot(MT, kwargs_dict) 3664 | 3665 | elif main_call == 'gmt': 3666 | return _call_gmt(MT, kwargs_dict) 3667 | 3668 | elif main_call == 'decompose': 3669 | return _call_decompose(MT, kwargs_dict) 3670 | 3671 | elif main_call == 'describe': 3672 | return _call_describe(MT, kwargs_dict) 3673 | 3674 | def _call_plot(MT, kwargs_dict): 3675 | 3676 | bb2plot = BeachBall(MT, kwargs_dict) 3677 | 3678 | if kwargs_dict['plot_save_plot']: 3679 | bb2plot.save_BB(kwargs_dict) 3680 | return 3681 | 3682 | if kwargs_dict['plot_pa_plot']: 3683 | bb2plot.pa_plot(kwargs_dict) 3684 | return 3685 | 3686 | if kwargs_dict['plot_full_sphere']: 3687 | bb2plot.full_sphere_plot(kwargs_dict) 3688 | return 3689 | 3690 | bb2plot.ploBB(kwargs_dict) 3691 | 3692 | return 3693 | 3694 | def _call_gmt(MT, kwargs_dict): 3695 | bb = BeachBall(MT, kwargs_dict) 3696 | return bb.get_psxy(kwargs_dict) 3697 | 3698 | def _call_decompose(MT, kwargs_dict): 3699 | 3700 | MT._isotropic = None 3701 | MT._deviatoric = None 3702 | MT._DC = None 3703 | MT._iso_percentage = None 3704 | MT._DC_percentage = None 3705 | MT._DC2 = None 3706 | MT._DC3 = None 3707 | MT._DC2_percentage = None 3708 | MT._CLVD = None 3709 | MT._seismic_moment = None 3710 | MT._moment_magnitude = None 3711 | 3712 | out_system = kwargs_dict['out_system'] 3713 | MT._output_basis = out_system 3714 | MT._decomposition_key = kwargs_dict['decomposition_key'] 3715 | 3716 | MT._decompose_M() 3717 | 3718 | print 3719 | 3720 | # build argument for local call within MT object: 3721 | lo_args = kwargs_dict['decomp_out_part'] 3722 | 3723 | if not lo_args: 3724 | lo_args = decomp_attrib_map_keys 3725 | 3726 | labels = '''Basis system of the input 3727 | Basis system of the output 3728 | Decomposition type 3729 | Full moment tensor 3730 | Isotropic part 3731 | Isotropic percentage 3732 | Deviatoric part 3733 | Deviatoric part 3734 | Deviatoric percentage 3735 | Double Couple part 3736 | Double Couple percentage 3737 | Second Double Couple part 3738 | Second Double Couple's percentage 3739 | Third Double Couple part 3740 | Third Double Couple's percentage 3741 | CLVD part in 3742 | CLVD percentage 3743 | Seismic moment (in Nm) 3744 | Moment magnitude ) 3745 | Eigenvalues 3746 | Eigenvectors 3747 | Tension-axis 3748 | Null-axis 3749 | Pressure-axis'''.splitlines() 3750 | 3751 | # for list of elements: 3752 | for label, arg in zip(labels, lo_args): 3753 | getter = getattr(MT, 'get_' + decomp_attrib_map[arg]) 3754 | x = getter(style='y', system=out_system) 3755 | print '%s: %s' % (label, x) 3756 | 3757 | def _call_describe(MT, kwargs_dict): 3758 | print MT 3759 | 3760 | def _build_gmt_dict(options, optparser): 3761 | """ 3762 | """ 3763 | consistent_kwargs_dict = {} 3764 | temp_dict = {} 3765 | lo_allowed_options = ['GMT_string_type', 'GMT_scaling', 3766 | 'GMT_tension_colour', 'GMT_pressure_colour', 3767 | 'GMT_show_2FP2', 'GMT_show_1FP', 3768 | 'plot_viewpoint', 'GMT_plot_isotropic_part', 3769 | 'GMT_projection'] 3770 | 3771 | # check for allowed options: 3772 | for ao in lo_allowed_options: 3773 | if hasattr(options, ao): 3774 | temp_dict[ao] = getattr(options, ao) 3775 | 3776 | if temp_dict['GMT_show_1FP']: 3777 | try: 3778 | if int(float(temp_dict['GMT_show_1FP'])) in [1, 2]: 3779 | consistent_kwargs_dict['_GMT_1fp'] = \ 3780 | int(float(temp_dict['GMT_show_1FP'])) 3781 | except: 3782 | pass 3783 | 3784 | if temp_dict['GMT_show_2FP2']: 3785 | temp_dict['GMT_show_1FP'] = 0 3786 | 3787 | consistent_kwargs_dict['_GMT_2fps'] = True 3788 | consistent_kwargs_dict['_GMT_1fp'] = 0 3789 | 3790 | if temp_dict['GMT_string_type'][0].lower() not in ['f', 'l', 'e']: 3791 | print 'type of desired string not known - taking "fill" instead' 3792 | consistent_kwargs_dict['_GMT_type'] = 'fill' 3793 | 3794 | else: 3795 | if temp_dict['GMT_string_type'][0] == 'f': 3796 | consistent_kwargs_dict['_GMT_type'] = 'fill' 3797 | elif temp_dict['GMT_string_type'][0] == 'l': 3798 | consistent_kwargs_dict['_GMT_type'] = 'lines' 3799 | else: 3800 | consistent_kwargs_dict['_GMT_type'] = 'EVs' 3801 | 3802 | if float(temp_dict['GMT_scaling']) < epsilon: 3803 | print 'GMT scaling factor must be a factor larger than' 3804 | print '%f - set to 1, due to obviously stupid input value' % \ 3805 | (epsilon) 3806 | temp_dict['GMT_scaling'] = 1 3807 | 3808 | if temp_dict['plot_viewpoint']: 3809 | try: 3810 | vp = temp_dict['plot_viewpoint'].split(',') 3811 | if not len(vp) == 3: 3812 | raise 3813 | if not -90 <= float(vp[0]) <= 90: 3814 | raise 3815 | if not -180 <= float(vp[1]) <= 180: 3816 | raise 3817 | if not 0 <= float(vp[2]) % 360 <= 360: 3818 | raise 3819 | consistent_kwargs_dict['plot_viewpoint'] = \ 3820 | [float(vp[0]), float(vp[1]), float(vp[2])] 3821 | except: 3822 | pass 3823 | 3824 | if temp_dict['GMT_projection']: 3825 | lo_allowed_projections = ['stereo', 'ortho', 'lambert'] # ,'gnom'] 3826 | do_allowed_projections = dict(zip(('s', 'o', 'l', 'g'), 3827 | ('stereo', 'ortho', 3828 | 'lambert', 'gnom'))) 3829 | try: 3830 | gmtp = temp_dict['GMT_projection'].lower() 3831 | if gmtp in lo_allowed_projections: 3832 | consistent_kwargs_dict['plot_projection'] = gmtp 3833 | elif gmtp in do_allowed_projections.keys(): 3834 | consistent_kwargs_dict['plot_projection'] = \ 3835 | do_allowed_projections[gmtp] 3836 | else: 3837 | consistent_kwargs_dict['plot_projection'] = 'stereo' 3838 | except: 3839 | pass 3840 | 3841 | consistent_kwargs_dict['_GMT_scaling'] = \ 3842 | temp_dict['GMT_scaling'] 3843 | consistent_kwargs_dict['_GMT_tension_colour'] = \ 3844 | temp_dict['GMT_tension_colour'] 3845 | consistent_kwargs_dict['_GMT_pressure_colour'] = \ 3846 | temp_dict['GMT_pressure_colour'] 3847 | consistent_kwargs_dict['_plot_isotropic_part'] = \ 3848 | temp_dict['GMT_plot_isotropic_part'] 3849 | 3850 | return consistent_kwargs_dict 3851 | 3852 | def _build_decompose_dict(options, optparser): 3853 | 3854 | consistent_kwargs_dict = {} 3855 | temp_dict = {} 3856 | lo_allowed_options = ['decomp_out_complete', 'decomp_out_fancy', 3857 | 'decomp_out_part', 'in_system', 'out_system', 3858 | 'decomp_key'] 3859 | 3860 | # check for allowed options: 3861 | for ao in lo_allowed_options: 3862 | if hasattr(options, ao): 3863 | temp_dict[ao] = getattr(options, ao) 3864 | 3865 | for k in 'in_system', 'out_system': 3866 | s = getattr(options, k).upper() 3867 | if s not in lo_allowed_systems: 3868 | sys.exit('Unavailable coordinate system: %s' % s) 3869 | 3870 | consistent_kwargs_dict[k] = s 3871 | 3872 | consistent_kwargs_dict['decomposition_key'] = int( 3873 | temp_dict['decomp_key']) 3874 | 3875 | if temp_dict['decomp_out_part'] is None: 3876 | consistent_kwargs_dict['decomp_out_part'] = None 3877 | else: 3878 | parts = [x.strip().lower() 3879 | for x in temp_dict['decomp_out_part'].split(',')] 3880 | for part in parts: 3881 | if part not in decomp_attrib_map_keys: 3882 | sys.exit('Unavailable decomposition part: %s' % part) 3883 | 3884 | consistent_kwargs_dict['decomp_out_part'] = parts 3885 | 3886 | consistent_kwargs_dict['style'] = 'y' 3887 | 3888 | return consistent_kwargs_dict 3889 | 3890 | def _build_plot_dict(options, optparser): 3891 | consistent_kwargs_dict = {} 3892 | temp_dict = {} 3893 | 3894 | lo_allowed_options = [ 3895 | 'plot_outfile', 'plot_pa_plot', 3896 | 'plot_full_sphere', 'plot_part_of_m', 'plot_viewpoint', 3897 | 'plot_projection', 'plot_show_upper_hemis', 'plot_n_points', 3898 | 'plot_size', 'plot_tension_colour', 'plot_pressure_colour', 3899 | 'plot_total_alpha', 'plot_show_faultplanes', 3900 | 'plot_show_1faultplane', 'plot_show_princ_axes', 3901 | 'plot_show_basis_axes', 'plot_outerline', 'plot_nodalline', 3902 | 'plot_dpi', 'plot_only_lines', 'plot_input_system', 3903 | 'plot_isotropic_part'] 3904 | 3905 | # check for allowed options: 3906 | for ao in lo_allowed_options: 3907 | if hasattr(options, ao): 3908 | temp_dict[ao] = getattr(options, ao) 3909 | 3910 | consistent_kwargs_dict['plot_save_plot'] = False 3911 | if temp_dict['plot_outfile']: 3912 | consistent_kwargs_dict['plot_save_plot'] = True 3913 | lo_possible_formats = ['svg', 'png', 'eps', 'pdf', 'ps'] 3914 | 3915 | try: 3916 | (filepath, filename) = os.path.split(temp_dict['plot_outfile']) 3917 | if not filename: 3918 | filename = 'dummy_filename.svg' 3919 | (shortname, extension) = os.path.splitext(filename) 3920 | if not shortname: 3921 | shortname = 'dummy_shortname' 3922 | 3923 | if extension[1:].lower() in lo_possible_formats: 3924 | consistent_kwargs_dict['plot_outfile_format'] = \ 3925 | extension[1:].lower() 3926 | 3927 | if shortname.endswith('.'): 3928 | consistent_kwargs_dict['plot_outfile'] = \ 3929 | os.path.realpath(os.path.abspath(os.path.join( 3930 | os.curdir, filepath, 3931 | shortname + extension[1:].lower()))) 3932 | else: 3933 | consistent_kwargs_dict['plot_outfile'] = \ 3934 | os.path.realpath(os.path.abspath(os.path.join( 3935 | os.curdir, filepath, shortname + '.' + 3936 | extension[1:].lower()))) 3937 | else: 3938 | if filename.endswith('.'): 3939 | consistent_kwargs_dict['plot_outfile'] = \ 3940 | os.path.realpath(os.path.abspath(os.path.join( 3941 | os.curdir, filepath, 3942 | filename + lo_possible_formats[0]))) 3943 | else: 3944 | consistent_kwargs_dict['plot_outfile'] = \ 3945 | os.path.realpath(os.path.abspath(os.path.join( 3946 | os.curdir, filepath, filename + '.' + 3947 | lo_possible_formats[0]))) 3948 | consistent_kwargs_dict['plot_outfile_format'] = \ 3949 | lo_possible_formats[0] 3950 | 3951 | except: 3952 | msg = 'please provide valid filename: . !!\n' 3953 | msg += ' must be svg, png, eps, pdf, or ps ' 3954 | exit(msg) 3955 | 3956 | if temp_dict['plot_pa_plot']: 3957 | consistent_kwargs_dict['plot_pa_plot'] = True 3958 | else: 3959 | consistent_kwargs_dict['plot_pa_plot'] = False 3960 | 3961 | if temp_dict['plot_full_sphere']: 3962 | consistent_kwargs_dict['plot_full_sphere'] = True 3963 | consistent_kwargs_dict['plot_pa_plot'] = False 3964 | else: 3965 | consistent_kwargs_dict['plot_full_sphere'] = False 3966 | 3967 | if temp_dict['plot_part_of_m']: 3968 | try: 3969 | plottable_part_raw = temp_dict['plot_part_of_m'].lower()[:2] 3970 | if plottable_part_raw == 'is': 3971 | plottable_part = 'iso' 3972 | elif plottable_part_raw == 'de': 3973 | plottable_part = 'devi' 3974 | elif plottable_part_raw == 'dc': 3975 | plottable_part = 'dc' 3976 | elif plottable_part_raw == 'cl': 3977 | plottable_part = 'clvd' 3978 | else: 3979 | plottable_part = False 3980 | 3981 | consistent_kwargs_dict['plot_part_of_m'] = plottable_part 3982 | 3983 | except: 3984 | consistent_kwargs_dict['plot_part_of_m'] = False 3985 | 3986 | else: 3987 | consistent_kwargs_dict['plot_part_of_m'] = False 3988 | 3989 | if temp_dict['plot_viewpoint']: 3990 | try: 3991 | vp = temp_dict['plot_viewpoint'].split(',') 3992 | if not len(vp) == 3: 3993 | raise 3994 | if not -90 <= float(vp[0]) <= 90: 3995 | raise 3996 | if not -180 <= float(vp[1]) <= 180: 3997 | raise 3998 | if not 0 <= float(vp[2]) % 360 <= 360: 3999 | raise 4000 | consistent_kwargs_dict['plot_viewpoint'] = \ 4001 | [float(vp[0]), float(vp[1]), float(vp[2])] 4002 | except: 4003 | pass 4004 | 4005 | if temp_dict['plot_projection']: 4006 | lo_allowed_projections = ['stereo', 'ortho', 'lambert'] # ,'gnom'] 4007 | do_allowed_projections = dict(zip(('s', 'o', 'l', 'g'), 4008 | ('stereo', 'ortho', 4009 | 'lambert', 'gnom'))) 4010 | try: 4011 | ppl = temp_dict['plot_projection'].lower() 4012 | if ppl in lo_allowed_projections: 4013 | consistent_kwargs_dict['plot_projection'] = ppl 4014 | elif ppl in do_allowed_projections.keys(): 4015 | consistent_kwargs_dict['plot_projection'] = \ 4016 | do_allowed_projections[ppl] 4017 | else: 4018 | consistent_kwargs_dict['plot_projection'] = 'stereo' 4019 | except: 4020 | pass 4021 | 4022 | if temp_dict['plot_show_upper_hemis']: 4023 | consistent_kwargs_dict['plot_show_upper_hemis'] = True 4024 | 4025 | if temp_dict['plot_n_points']: 4026 | try: 4027 | if temp_dict['plot_n_points'] > 360: 4028 | consistent_kwargs_dict['plot_n_points'] = \ 4029 | int(temp_dict['plot_n_points']) 4030 | except: 4031 | pass 4032 | 4033 | if temp_dict['plot_size']: 4034 | try: 4035 | if 0.01 < temp_dict['plot_size'] <= 1: 4036 | consistent_kwargs_dict['plot_size'] = \ 4037 | temp_dict['plot_size'] * 10 / 2.54 4038 | elif 1 < temp_dict['plot_size'] < 45: 4039 | consistent_kwargs_dict['plot_size'] = \ 4040 | temp_dict['plot_size'] / 2.54 4041 | else: 4042 | consistent_kwargs_dict['plot_size'] = 5 4043 | consistent_kwargs_dict['plot_aux_plot_size'] = \ 4044 | consistent_kwargs_dict['plot_size'] 4045 | except: 4046 | pass 4047 | 4048 | if temp_dict['plot_pressure_colour']: 4049 | try: 4050 | sec_colour_raw = temp_dict['plot_pressure_colour'].split(',') 4051 | if len(sec_colour_raw) == 1: 4052 | if sec_colour_raw[0].lower()[0] in list('bgrcmykw'): 4053 | consistent_kwargs_dict['plot_pressure_colour'] = \ 4054 | sec_colour_raw[0].lower()[0] 4055 | else: 4056 | raise 4057 | elif len(sec_colour_raw) == 3: 4058 | for sc in sec_colour_raw: 4059 | if not 0 <= (int(sc)) <= 255: 4060 | raise 4061 | consistent_kwargs_dict['plot_pressure_colour'] = \ 4062 | (float(sec_colour_raw[0]) / 255., 4063 | float(sec_colour_raw[1]) / 255., 4064 | float(sec_colour_raw[2]) / 255.) 4065 | else: 4066 | raise 4067 | except: 4068 | pass 4069 | 4070 | if temp_dict['plot_tension_colour']: 4071 | try: 4072 | sec_colour_raw = temp_dict['plot_tension_colour'].split(',') 4073 | if len(sec_colour_raw) == 1: 4074 | if sec_colour_raw[0].lower()[0] in list('bgrcmykw'): 4075 | consistent_kwargs_dict['plot_tension_colour'] = \ 4076 | sec_colour_raw[0].lower()[0] 4077 | else: 4078 | raise 4079 | elif len(sec_colour_raw) == 3: 4080 | for sc in sec_colour_raw: 4081 | if not 0 <= (int(float(sc))) <= 255: 4082 | raise 4083 | consistent_kwargs_dict['plot_tension_colour'] = \ 4084 | (float(sec_colour_raw[0]) / 255., 4085 | float(sec_colour_raw[1]) / 255., 4086 | float(sec_colour_raw[2]) / 255.) 4087 | else: 4088 | raise 4089 | except: 4090 | pass 4091 | 4092 | if temp_dict['plot_total_alpha']: 4093 | try: 4094 | if not 0 <= float(temp_dict['plot_total_alpha']) <= 1: 4095 | consistent_kwargs_dict['plot_total_alpha'] = 1 4096 | else: 4097 | consistent_kwargs_dict['plot_total_alpha'] = \ 4098 | float(temp_dict['plot_total_alpha']) 4099 | except: 4100 | pass 4101 | 4102 | if temp_dict['plot_show_1faultplane']: 4103 | consistent_kwargs_dict['plot_show_1faultplane'] = True 4104 | try: 4105 | fp_args = temp_dict['plot_show_1faultplane'] 4106 | 4107 | if not int(fp_args[0]) in [1, 2]: 4108 | consistent_kwargs_dict['plot_show_FP_index'] = 1 4109 | else: 4110 | consistent_kwargs_dict['plot_show_FP_index'] = \ 4111 | int(fp_args[0]) 4112 | 4113 | if not 0 < float(fp_args[1]) <= 20: 4114 | consistent_kwargs_dict['plot_faultplane_width'] = 2 4115 | else: 4116 | consistent_kwargs_dict['plot_faultplane_width'] = \ 4117 | float(fp_args[1]) 4118 | 4119 | try: 4120 | sec_colour_raw = fp_args[2].split(',') 4121 | if len(sec_colour_raw) == 1: 4122 | if sec_colour_raw[0].lower()[0] in list('bgrcmykw'): 4123 | consistent_kwargs_dict['plot_faultplane_colour'] =\ 4124 | sec_colour_raw[0].lower()[0] 4125 | else: 4126 | raise 4127 | elif len(sec_colour_raw) == 3: 4128 | for sc in sec_colour_raw: 4129 | if not 0 <= (int(sc)) <= 255: 4130 | raise 4131 | consistent_kwargs_dict['plot_faultplane_colour'] = \ 4132 | (float(sec_colour_raw[0]) / 255., 4133 | float(sec_colour_raw[1]) / 255., 4134 | float(sec_colour_raw[2]) / 255.) 4135 | else: 4136 | raise 4137 | except: 4138 | consistent_kwargs_dict['plot_faultplane_colour'] = 'k' 4139 | 4140 | try: 4141 | if 0 <= float(fp_args[3]) <= 1: 4142 | consistent_kwargs_dict['plot_faultplane_alpha'] = \ 4143 | float(fp_args[3]) 4144 | except: 4145 | consistent_kwargs_dict['plot_faultplane_alpha'] = 1 4146 | except: 4147 | pass 4148 | 4149 | if temp_dict['plot_show_faultplanes']: 4150 | consistent_kwargs_dict['plot_show_faultplanes'] = True 4151 | consistent_kwargs_dict['plot_show_1faultplane'] = False 4152 | 4153 | if temp_dict['plot_dpi']: 4154 | try: 4155 | if 200 <= int(temp_dict['plot_dpi']) <= 2000: 4156 | consistent_kwargs_dict['plot_dpi'] = \ 4157 | int(temp_dict['plot_dpi']) 4158 | else: 4159 | raise 4160 | except: 4161 | pass 4162 | 4163 | if temp_dict['plot_only_lines']: 4164 | consistent_kwargs_dict['plot_fill_flag'] = False 4165 | 4166 | if temp_dict['plot_outerline']: 4167 | consistent_kwargs_dict['plot_outerline'] = True 4168 | try: 4169 | fp_args = temp_dict['plot_outerline'] 4170 | if not 0 < float(fp_args[0]) <= 20: 4171 | consistent_kwargs_dict['plot_outerline_width'] = 2 4172 | else: 4173 | consistent_kwargs_dict['plot_outerline_width'] = \ 4174 | float(fp_args[0]) 4175 | try: 4176 | sec_colour_raw = fp_args[1].split(',') 4177 | if len(sec_colour_raw) == 1: 4178 | if sec_colour_raw[0].lower()[0] in list('bgrcmykw'): 4179 | consistent_kwargs_dict['plot_outerline_colour'] = \ 4180 | sec_colour_raw[0].lower()[0] 4181 | else: 4182 | raise 4183 | elif len(sec_colour_raw) == 3: 4184 | for sc in sec_colour_raw: 4185 | if not 0 <= (int(sc)) <= 255: 4186 | raise 4187 | consistent_kwargs_dict['plot_outerline_colour'] = \ 4188 | (float(sec_colour_raw[0]) / 255., 4189 | float(sec_colour_raw[1]) / 255., 4190 | float(sec_colour_raw[2]) / 255.) 4191 | else: 4192 | raise 4193 | except: 4194 | consistent_kwargs_dict['plot_outerline_colour'] = 'k' 4195 | 4196 | try: 4197 | if 0 <= float(fp_args[2]) <= 1: 4198 | consistent_kwargs_dict['plot_outerline_alpha'] = \ 4199 | float(fp_args[2]) 4200 | except: 4201 | consistent_kwargs_dict['plot_outerline_alpha'] = 1 4202 | except: 4203 | pass 4204 | 4205 | if temp_dict['plot_nodalline']: 4206 | consistent_kwargs_dict['plot_nodalline'] = True 4207 | try: 4208 | fp_args = temp_dict['plot_nodalline'] 4209 | 4210 | if not 0 < float(fp_args[0]) <= 20: 4211 | consistent_kwargs_dict['plot_nodalline_width'] = 2 4212 | else: 4213 | consistent_kwargs_dict['plot_nodalline_width'] = \ 4214 | float(fp_args[0]) 4215 | try: 4216 | sec_colour_raw = fp_args[1].split(',') 4217 | if len(sec_colour_raw) == 1: 4218 | if sec_colour_raw[0].lower()[0] in list('bgrcmykw'): 4219 | consistent_kwargs_dict['plot_nodalline_colour'] = \ 4220 | sec_colour_raw[0].lower()[0] 4221 | else: 4222 | raise 4223 | elif len(sec_colour_raw) == 3: 4224 | for sc in sec_colour_raw: 4225 | if not 0 <= (int(sc)) <= 255: 4226 | raise 4227 | consistent_kwargs_dict['plot_nodalline_colour'] = \ 4228 | (float(sec_colour_raw[0]) / 255., 4229 | float(sec_colour_raw[1]) / 255., 4230 | float(sec_colour_raw[2]) / 255.) 4231 | else: 4232 | raise 4233 | except: 4234 | consistent_kwargs_dict['plot_nodalline_colour'] = 'k' 4235 | try: 4236 | if 0 <= float(fp_args[2]) <= 1: 4237 | consistent_kwargs_dict['plot_nodalline_alpha'] = \ 4238 | float(fp_args[2]) 4239 | except: 4240 | consistent_kwargs_dict['plot_nodalline_alpha'] = 1 4241 | except: 4242 | pass 4243 | 4244 | if temp_dict['plot_show_princ_axes']: 4245 | consistent_kwargs_dict['plot_show_princ_axes'] = True 4246 | try: 4247 | fp_args = temp_dict['plot_show_princ_axes'] 4248 | 4249 | if not 0 < float(fp_args[0]) <= 40: 4250 | consistent_kwargs_dict['plot_princ_axes_symsize'] = 10 4251 | else: 4252 | consistent_kwargs_dict['plot_princ_axes_symsize'] = \ 4253 | float(fp_args[0]) 4254 | 4255 | if not 0 < float(fp_args[1]) <= 20: 4256 | consistent_kwargs_dict['plot_princ_axes_lw '] = 3 4257 | else: 4258 | consistent_kwargs_dict['plot_princ_axes_lw '] = \ 4259 | float(fp_args[1]) 4260 | try: 4261 | if 0 <= float(fp_args[2]) <= 1: 4262 | consistent_kwargs_dict['plot_princ_axes_alpha'] = \ 4263 | float(fp_args[2]) 4264 | except: 4265 | consistent_kwargs_dict['plot_princ_axes_alpha'] = 1 4266 | except: 4267 | pass 4268 | 4269 | if temp_dict['plot_show_basis_axes']: 4270 | consistent_kwargs_dict['plot_show_basis_axes'] = True 4271 | 4272 | if temp_dict['plot_input_system']: 4273 | lo_allowed_systems = ['XYZ', 'NED', 'USE', 'NWU'] 4274 | try: 4275 | tpis = temp_dict['plot_input_system'][:3].upper() 4276 | if tpis in lo_allowed_systems: 4277 | consistent_kwargs_dict['in_system'] = tpis 4278 | else: 4279 | raise 4280 | except: 4281 | pass 4282 | 4283 | if temp_dict['plot_isotropic_part']: 4284 | consistent_kwargs_dict['plot_isotropic_part'] = \ 4285 | temp_dict['plot_isotropic_part'] 4286 | 4287 | return consistent_kwargs_dict 4288 | 4289 | def _build_describe_dict(options, optparser): 4290 | consistent_kwargs_dict = {} 4291 | 4292 | for k in 'in_system', 'out_system': 4293 | s = getattr(options, k).upper() 4294 | if s not in lo_allowed_systems: 4295 | sys.exit('Unavailable coordinate system: %s' % s) 4296 | 4297 | consistent_kwargs_dict[k] = s 4298 | 4299 | return consistent_kwargs_dict 4300 | 4301 | def _parse_arguments(main_call, its_arguments, optparser): 4302 | """ 4303 | """ 4304 | # todo: 4305 | # print '\n', main_call,its_arguments,'\n' 4306 | (options, args) = optparser.parse_args(its_arguments) 4307 | 4308 | # todo 4309 | # check, if arguments do not start with "-" - if so, there is a lack of 4310 | # arguments for the previous option 4311 | for val2check in options.__dict__.values(): 4312 | if str(val2check).startswith('-'): 4313 | try: 4314 | val2check_split = val2check.split(',') 4315 | for ii in val2check_split: 4316 | float(ii) 4317 | except: 4318 | sys.exit('ERROR - check carefully number of arguments ' + 4319 | 'for all options\n') 4320 | 4321 | if main_call == 'plot': 4322 | consistent_kwargs_dict = _build_plot_dict(options, optparser) 4323 | 4324 | elif main_call == 'gmt': 4325 | consistent_kwargs_dict = _build_gmt_dict(options, optparser) 4326 | 4327 | elif main_call == 'decompose': 4328 | consistent_kwargs_dict = _build_decompose_dict(options, optparser) 4329 | 4330 | elif main_call == 'describe': 4331 | consistent_kwargs_dict = _build_describe_dict(options, optparser) 4332 | 4333 | return consistent_kwargs_dict 4334 | 4335 | def _add_group_system(parent): 4336 | group_system = OptionGroup(parent, 'Basis systems') 4337 | group_system.add_option('-i', '--input-system', 4338 | action="store", 4339 | dest='in_system', 4340 | metavar='', 4341 | default='NED', 4342 | help=''' 4343 | Define the coordinate system of the source mechanism [Default: NED]. 4344 | 4345 | Available coordinate systems: 4346 | 4347 | * NED: North, East, Down 4348 | * USE: Up, South, East (Global CMT) 4349 | * XYZ: East, North, Up (Jost and Herrmann) 4350 | * NWU: North, West, Up (Stein and Wysession) 4351 | '''.lstrip()) 4352 | 4353 | group_system.add_option('-o', '--output-system', 4354 | action="store", 4355 | dest='out_system', 4356 | metavar='', 4357 | default='NED', 4358 | help=''' 4359 | Define the coordinate system of the output. See '--input-system' for a list of 4360 | available coordinate systems [Default: NED].'''.lstrip()) 4361 | 4362 | parent.add_option_group(group_system) 4363 | 4364 | def _build_optparsers(): 4365 | 4366 | _do_parsers = {} 4367 | 4368 | desc = """ 4369 | Generate a beachball representation which can be plotted with GMT. 4370 | 4371 | This tool produces output which can be fed into the GMT command `psxy`. The 4372 | output consists of coordinates which describe the lines of the beachball in 4373 | standard cartesian coordinates, centered at zero. 4374 | 4375 | In order to generate a beachball diagram, this tool has to be called twice with 4376 | different arguments of the --type option. First to define the colored areas 4377 | (--type=fill) and second for the nodal and border lines (--type=lines). 4378 | 4379 | Example: 4380 | 4381 | mopad gmt 30,60,90 --type=fill | \ 4382 | psxy -Jx4/4 -R-2/2/-2/2 -P -Cpsxy_fill.cpt -M -L -K > out.ps 4383 | 4384 | mopad gmt 30,60,90 --type=lines | \ 4385 | psxy -Jx4/4 -R-2/2/-2/2 -P -Cpsxy_lines.cpt -W2p -P -M -O >> out.ps 4386 | 4387 | """ 4388 | 4389 | parser_gmt = OptionParser( 4390 | usage='mopad.py gmt [options]', 4391 | description=desc, formatter=MopadHelpFormatter()) 4392 | 4393 | group_type = OptionGroup(parser_gmt, 'Output') 4394 | group_show = OptionGroup(parser_gmt, 'Appearance') 4395 | group_geo = OptionGroup(parser_gmt, 'Geometry') 4396 | 4397 | group_type.add_option('-t', '--type', 4398 | type='string', 4399 | dest='GMT_string_type', 4400 | action='store', 4401 | default='fill', 4402 | help='Chosing the respective psxy data set: ' 4403 | 'area to fill (fill), nodal lines (lines), ' 4404 | 'or eigenvector positions (ev) [Default: fill]', 4405 | metavar='') 4406 | 4407 | group_show.add_option('-s', '--scaling', 4408 | dest='GMT_scaling', 4409 | action='store', 4410 | default='1', 4411 | type='float', 4412 | metavar='', 4413 | help='Spatial scaling factor of the beachball ' 4414 | '[Default: 1]') 4415 | group_show.add_option('-r', '--colour1', 4416 | dest='GMT_tension_colour', 4417 | type='int', 4418 | action='store', 4419 | metavar='', 4420 | default='1', 4421 | help="-Z option's key (see help for 'psxy') " 4422 | 'for the tension colour of the beachball - ' 4423 | 'type: integer [Default: 1]') 4424 | group_show.add_option('-w', '--colour2', 4425 | dest='GMT_pressure_colour', 4426 | type='int', 4427 | action='store', 4428 | metavar='', 4429 | default='0', 4430 | help="-Z option's key (see help for 'psxy') " 4431 | 'for the pressure colour of the beachball - ' 4432 | 'type: integer [Default: 0]') 4433 | group_show.add_option('-D', '--faultplanes', 4434 | dest='GMT_show_2FP2', 4435 | action='store_true', 4436 | default=False, 4437 | help='Key, if 2 faultplanes shall be shown ' 4438 | '[Default: deactivated]') 4439 | group_show.add_option('-d', '--show_1fp', 4440 | type='choice', 4441 | dest='GMT_show_1FP', 4442 | choices=['1', '2'], 4443 | metavar='', 4444 | action='store', 4445 | default=False, 4446 | help='Key for plotting 1 specific faultplane - ' 4447 | 'value: 1,2 [Default: None]') 4448 | group_geo.add_option('-v', '--viewpoint', 4449 | action='store', 4450 | dest='plot_viewpoint', 4451 | metavar='', 4452 | default=None, 4453 | help='Coordinates (in degrees) of the viewpoint ' 4454 | 'onto the projection - type: comma separated ' 4455 | '3-tuple [Default: None]') 4456 | group_geo.add_option('-p', '--projection', 4457 | action='store', 4458 | dest='GMT_projection', 4459 | metavar='', 4460 | default=None, 4461 | help='Two-dimensional projection of the sphere - ' 4462 | 'value: (s)tereographic, (l)ambert, ' 4463 | '(o)rthographic [Default: (s)tereographic]') 4464 | group_show.add_option('-I', '--show_isotropic_part', 4465 | dest='GMT_plot_isotropic_part', 4466 | action='store_true', 4467 | default=False, 4468 | help='Key for considering the isotropic part ' 4469 | 'for plotting [Default: deactivated]') 4470 | 4471 | parser_gmt.add_option_group(group_type) 4472 | parser_gmt.add_option_group(group_show) 4473 | parser_gmt.add_option_group(group_geo) 4474 | 4475 | _do_parsers['gmt'] = parser_gmt 4476 | 4477 | # plot 4478 | desc_plot = """ 4479 | Plot a beachball diagram of the provided mechanism. 4480 | 4481 | Several styles and configurations are available. Also saving 4482 | on the fly can be enabled. 4483 | ONLY THE DEVIATORIC COMPONENT WILL BE PLOTTED by default; 4484 | for including the isotropic part, use the '--show_isotropic_part' 4485 | option! 4486 | """ 4487 | parser_plot = OptionParser( 4488 | usage="mopad.py plot [options]", 4489 | description=desc_plot, formatter=MopadHelpFormatter()) 4490 | 4491 | group_save = OptionGroup(parser_plot, 'Saving') 4492 | group_type = OptionGroup(parser_plot, 'Type of plot') 4493 | group_quality = OptionGroup(parser_plot, 'Quality') 4494 | group_colours = OptionGroup(parser_plot, 'Colours') 4495 | group_misc = OptionGroup(parser_plot, 'Miscellaneous') 4496 | group_dc = OptionGroup(parser_plot, 'Fault planes') 4497 | group_geo = OptionGroup(parser_plot, 'Geometry') 4498 | group_app = OptionGroup(parser_plot, 'Appearance') 4499 | 4500 | group_save.add_option('-f', '--output_file', 4501 | action="store", 4502 | dest='plot_outfile', 4503 | metavar='', 4504 | default=None, 4505 | nargs=1, 4506 | help='(Absolute) filename for saving ' 4507 | '[Default: None]') 4508 | 4509 | group_type.add_option('-E', '--eigen_system', 4510 | action="store_true", 4511 | dest='plot_pa_plot', 4512 | default=False, 4513 | help='Key for plotting principal axis ' 4514 | 'system/eigensystem [Default: deactivated]') 4515 | 4516 | group_type.add_option('-O', '--full_sphere', 4517 | action="store_true", 4518 | dest='plot_full_sphere', 4519 | default=False, 4520 | help='Key for plotting the full sphere ' 4521 | '[Default: deactivated]') 4522 | 4523 | group_type.add_option('-P', '--partial', 4524 | action="store", 4525 | dest='plot_part_of_m', 4526 | metavar='', 4527 | default=None, 4528 | help='Key for plotting only a specific part of ' 4529 | 'M - values: iso,devi,dc,clvd [Default: None] ') 4530 | 4531 | group_geo.add_option('-v', '--viewpoint', 4532 | action="store", 4533 | dest='plot_viewpoint', 4534 | metavar='', 4535 | default=None, 4536 | help='Coordinates (in degrees) of the viewpoint ' 4537 | 'onto the projection - type: comma separated ' 4538 | '3-tuple [Default: None]') 4539 | 4540 | group_geo.add_option('-p', '--projection', 4541 | action="store", 4542 | dest='plot_projection', 4543 | metavar='', 4544 | default=None, 4545 | help='Two-dimensional projection of the sphere ' 4546 | '- value: (s)tereographic, (l)ambert, ' 4547 | '(o)rthographic [Default: (s)tereographic]') 4548 | 4549 | group_type.add_option('-U', '--upper', 4550 | action="store_true", 4551 | dest='plot_show_upper_hemis', 4552 | default=False, 4553 | help='Key for plotting the upper hemisphere ' 4554 | '[Default: deactivated]') 4555 | 4556 | group_quality.add_option('-N', '--points', 4557 | action="store", 4558 | metavar='', 4559 | dest='plot_n_points', 4560 | type="int", 4561 | default=None, 4562 | help='Minimum number of points, used for ' 4563 | 'nodal lines [Default: None]') 4564 | 4565 | group_app.add_option('-s', '--size', 4566 | action="store", 4567 | dest='plot_size', 4568 | metavar='', 4569 | type="float", 4570 | default=None, 4571 | help='Size of plot (diameter) in cm ' 4572 | '[Default: None]') 4573 | 4574 | group_colours.add_option('-w', '--pressure_colour', 4575 | action="store", 4576 | dest='plot_pressure_colour', 4577 | metavar='', 4578 | default=None, 4579 | help='Colour of the tension area - values: ' 4580 | 'comma separated RGB 3-tuples OR MATLAB ' 4581 | 'conform colour names [Default: None]') 4582 | 4583 | group_colours.add_option('-r', '--tension_colour', 4584 | action="store", 4585 | dest='plot_tension_colour', 4586 | metavar='', 4587 | default=None, 4588 | help='Colour of the pressure area values: ' 4589 | 'comma separated RGB 3-tuples OR MATLAB ' 4590 | 'conform colour names [Default: None]') 4591 | 4592 | group_app.add_option('-a', '--alpha', 4593 | action="store", 4594 | dest='plot_total_alpha', 4595 | metavar='', 4596 | type='float', 4597 | default=None, 4598 | help='Alpha value for the total plot - value: ' 4599 | 'float between 1=opaque to 0=transparent ' 4600 | '[Default: None]') 4601 | 4602 | group_dc.add_option('-D', '--dc', 4603 | action="store_true", 4604 | dest='plot_show_faultplanes', 4605 | default=False, 4606 | help='Key for plotting both double couple ' 4607 | 'faultplanes (blue) [Default: deactivated]') 4608 | 4609 | group_dc.add_option('-d', '--show1fp', 4610 | action="store", 4611 | metavar=' ', 4612 | dest='plot_show_1faultplane', 4613 | default=None, 4614 | nargs=4, 4615 | help='Key for plotting 1 specific faultplane - ' 4616 | '4 arguments as space separated list - ' 4617 | 'index values: 1,2, linewidth value: float, ' 4618 | 'line colour value: string or RGB-3-tuple, alpha ' 4619 | 'value: float between 0 and 1 [Default: None] ') 4620 | 4621 | group_misc.add_option('-e', '--eigenvectors', 4622 | action="store", 4623 | dest='plot_show_princ_axes', 4624 | metavar=' ', 4625 | default=None, 4626 | nargs=3, 4627 | help='Key for showing eigenvectors - ' 4628 | '3 arguments as space separated list - symbol ' 4629 | 'size value: float, symbol linewidth value: ' 4630 | 'float, symbol alpha value: float between 0 ' 4631 | 'and 1 [Default: None]') 4632 | 4633 | group_misc.add_option('-b', '--basis_vectors', 4634 | action="store_true", 4635 | dest='plot_show_basis_axes', 4636 | default=False, 4637 | help='Key for showing NED basis axes in plot ' 4638 | '[Default: deactivated]') 4639 | 4640 | group_app.add_option('-l', '--lines', 4641 | action="store", 4642 | dest='plot_outerline', 4643 | metavar=' ', 4644 | nargs=3, 4645 | default=None, 4646 | help='Define the style of the outer line - ' 4647 | '3 arguments as space separated list - ' 4648 | 'linewidth value: float, line colour value: ' 4649 | 'string or RGB-3-tuple), alpha value: float ' 4650 | 'between 0 and 1 [Default: None]') 4651 | 4652 | group_app.add_option('-n', '--nodals', 4653 | action="store", 4654 | dest='plot_nodalline', 4655 | metavar=' ', 4656 | default=None, 4657 | nargs=3, 4658 | help='Define the style of the nodal lines - 3 ' 4659 | 'arguments as space separated list - linewidth ' 4660 | 'value: float, line colour value: string or ' 4661 | 'RGB-3-tuple), alpha value: float between 0 and ' 4662 | '1 [Default: None]') 4663 | 4664 | group_quality.add_option('-Q', '--quality', 4665 | action="store", 4666 | dest='plot_dpi', 4667 | metavar='', 4668 | type="int", 4669 | default=None, 4670 | help='Set the quality for the plot in ' 4671 | 'terms of dpi (minimum=200) [Default: None] ') 4672 | 4673 | group_type.add_option('-L', '--lines_only', 4674 | action="store_true", 4675 | dest='plot_only_lines', 4676 | default=False, 4677 | help='Key for plotting lines only (no filling ' 4678 | '- this overwrites all "fill"-related options) ' 4679 | '[Default: deactivated] ') 4680 | 4681 | group_misc.add_option('-i', '--input-system', 4682 | action="store", 4683 | dest='plot_input_system', 4684 | metavar='', 4685 | default=False, 4686 | help='Define the coordinate system of the ' 4687 | 'source mechanism - value: NED,USE,XYZ,NWU ' 4688 | '[Default: NED] ') 4689 | 4690 | group_type.add_option('-I', '--show_isotropic_part', 4691 | dest='plot_isotropic_part', 4692 | action='store_true', 4693 | default=False, 4694 | help='Key for considering the isotropic part ' 4695 | 'for plotting [Default: deactivated]') 4696 | 4697 | parser_plot.add_option_group(group_save) 4698 | parser_plot.add_option_group(group_type) 4699 | parser_plot.add_option_group(group_quality) 4700 | parser_plot.add_option_group(group_colours) 4701 | parser_plot.add_option_group(group_misc) 4702 | parser_plot.add_option_group(group_dc) 4703 | parser_plot.add_option_group(group_geo) 4704 | parser_plot.add_option_group(group_app) 4705 | 4706 | _do_parsers['plot'] = parser_plot 4707 | 4708 | desc_decomp = """ 4709 | Decompose moment tensor into additive contributions. 4710 | 4711 | This method implements four different decompositions following the conventions 4712 | given by Jost & Herrmann (1998), and Dahm (1997). The type of decomposition can 4713 | be selected with the '--type' option. Use the '--partial' option, if only parts 4714 | of the full decomposition are required. 4715 | 4716 | By default, the decomposition results are printed in the following order: 4717 | 4718 | * 01 - basis of the provided input (string) 4719 | * 02 - basis of the representation (string) 4720 | * 03 - chosen decomposition type (integer) 4721 | 4722 | * 04 - full moment tensor (matrix) 4723 | 4724 | * 05 - isotropic part (matrix) 4725 | * 06 - isotropic percentage (float) 4726 | * 07 - deviatoric part (matrix) 4727 | * 08 - deviatoric percentage (float) 4728 | 4729 | * 09 - DC part (matrix) 4730 | * 10 - DC percentage (float) 4731 | * 11 - DC2 part (matrix) 4732 | * 12 - DC2 percentage (float) 4733 | * 13 - DC3 part (matrix) 4734 | * 14 - DC3 percentage (float) 4735 | 4736 | * 15 - CLVD part (matrix) 4737 | * 16 - CLVD percentage (matrix) 4738 | 4739 | * 17 - seismic moment (float) 4740 | * 18 - moment magnitude (float) 4741 | 4742 | * 19 - eigenvectors (3-array) 4743 | * 20 - eigenvalues (list) 4744 | * 21 - p-axis (3-array) 4745 | * 22 - neutral axis (3-array) 4746 | * 23 - t-axis (3-array) 4747 | """ 4748 | 4749 | parser_decompose = OptionParser( 4750 | usage="mopad decompose [options]", 4751 | description=desc_decomp, formatter=MopadHelpFormatter()) 4752 | 4753 | group_type = OptionGroup( 4754 | parser_decompose, 'Type of decomposition') 4755 | group_part = OptionGroup( 4756 | parser_decompose, 'Output selection') 4757 | 4758 | group_part.add_option('-p', '--partial', 4759 | action="store", 4760 | dest='decomp_out_part', 4761 | default=None, 4762 | metavar='', 4763 | help=''' 4764 | Print a subset of the decomposition results. 4765 | 4766 | Give a comma separated list of what parts of the results should be 4767 | printed [Default: None]. The following parts are available: 4768 | 4769 | %s 4770 | ''' % ', '.join(decomp_attrib_map_keys)) 4771 | 4772 | group_type.add_option('-t', '--type', 4773 | action="store", 4774 | dest='decomp_key', 4775 | metavar='', 4776 | default=1, 4777 | type='int', 4778 | help=''' 4779 | Choose type of decomposition - values 1,2,3,4 \n[Default: 1]: 4780 | 4781 | %s 4782 | ''' % '\n'.join([' * %s - %s' % (k, v[0]) for (k, v) 4783 | in MomentTensor.decomp_dict.items()])) 4784 | 4785 | parser_decompose.add_option_group(group_type) 4786 | parser_decompose.add_option_group(group_part) 4787 | _add_group_system(parser_decompose) 4788 | 4789 | _do_parsers['decompose'] = parser_decompose 4790 | 4791 | parser_describe = OptionParser( 4792 | usage="mopad describe [options]", 4793 | description=''' 4794 | Print the detailed description of a source mechanism 4795 | 4796 | 4797 | For a given source mechanism, orientations of the fault planes, moment, 4798 | magnitude, and moment tensor are printed. Input and output coordinate basis 4799 | systems can be specified.'''.lstrip(), 4800 | formatter=MopadHelpFormatter()) 4801 | 4802 | _add_group_system(parser_describe) 4803 | 4804 | _do_parsers['describe'] = parser_describe 4805 | 4806 | return _do_parsers 4807 | 4808 | if len(sys.argv) < 2: 4809 | call = 'help' 4810 | 4811 | else: 4812 | 4813 | call = sys.argv[1].lower() 4814 | abbrev = dict(zip(('p', 'g', 'd', 'i', '--help', '-h'), ( 4815 | 'plot', 'gmt', 'decompose', 'describe', 'help', 'help'))) 4816 | 4817 | if call in abbrev: 4818 | call = abbrev[call] 4819 | 4820 | if call not in abbrev.values(): 4821 | sys.exit('no such method: %s' % call) 4822 | 4823 | if call == 'help': 4824 | helpstring = """ 4825 | 4826 | Usage: mopad [options] 4827 | 4828 | 4829 | Type 'mopad --help' for help on a specific method. 4830 | 4831 | 4832 | MoPaD (version %.1f) - Moment Tensor Plotting and Decomposition Tool 4833 | 4834 | MoPaD is a tool to plot and decompose moment tensor representations of seismic 4835 | sources which are commonly used in seismology. This tool is completely 4836 | controlled via command line parameters, which consist of a and a 4837 | argument and zero or more options. The argument 4838 | tells MoPaD what to do and the argument specifies an input 4839 | moment tensor source in one of the formats described below. 4840 | 4841 | Available methods: 4842 | 4843 | * plot: plot a beachball representation of a source mechanism 4844 | * describe: print detailed description of a source mechanism 4845 | * decompose: decompose a source mechanism according to various conventions 4846 | * gmt: output beachball representation in a format suitable for 4847 | plotting with GMT 4848 | 4849 | The source-mechanism is given as a comma separated list (NO BLANK SPACES!) of 4850 | values which is interpreted differently, according to the number of values in 4851 | the list. The following source-mechanism representations are available: 4852 | 4853 | * strike,dip,rake 4854 | * strike,dip,rake,moment 4855 | * M11,M22,M33,M12,M13,M23 4856 | * M11,M22,M33,M12,M13,M23,moment 4857 | * M11,M12,M13,M21,M22,M23,M31,M32,M33 4858 | 4859 | Angles are given in degrees, moment tensor components and scalar moment are 4860 | given in [Nm] for a coordinate system with axes pointing North, East, and Down 4861 | by default. 4862 | _______________________________________________________________________________ 4863 | 4864 | EXAMPLES 4865 | -------- 4866 | 4867 | 'plot' : 4868 | -- 4869 | To generate the "beachball" representation of a pure normal faulting event with 4870 | a strike angle of 0 degrees and a dip of 45 degrees, use either of the 4871 | following commands: 4872 | 4873 | mopad plot 0,45,-90 4874 | 4875 | mopad plot 0,1,-1,0,0,0 4876 | 4877 | 4878 | 'describe': 4879 | -- 4880 | To see the seismic moment tensor entries (in GlobalCMT's USE basis) and the 4881 | orientation of the auxilliary plane for a shear crack with the 4882 | (strike,dip,slip-rake) tuple (90,45,45) use: 4883 | 4884 | mopad describe 90,45,45 -o USE 4885 | 4886 | 4887 | 'decompose': 4888 | -- 4889 | Get the deviatoric part of a seismic moment tensor M=(1,2,3,4,5,6) together 4890 | with the respective double-couple- and CLVD-components by using: 4891 | 4892 | mopad decompose 1,2,3,4,5,6 -p devi,dc,clvd 4893 | 4894 | 4895 | """ % (MOPAD_VERSION) 4896 | 4897 | print helpstring 4898 | 4899 | sys.exit() 4900 | 4901 | try: 4902 | M_raw = [float(xx) for xx in sys.argv[2].split(',')] 4903 | except: 4904 | dummy_list = [] 4905 | dummy_list.append(sys.argv[0]) 4906 | dummy_list.append(sys.argv[1]) 4907 | dummy_list.append('0,0,0') 4908 | dummy_list.append('-h') 4909 | 4910 | sys.argv = dummy_list 4911 | M_raw = [float(xx) for xx in sys.argv[2].split(',')] 4912 | 4913 | if not len(M_raw) in [3, 4, 6, 7, 9]: 4914 | print '\nERROR!! Provide proper source mechanism\n\n' 4915 | sys.exit() 4916 | if len(M_raw) in [4, 6, 7, 9] and len(np.array(M_raw).nonzero()[0]) == 0: 4917 | print '\nERROR!! Provide proper source mechanism\n\n' 4918 | sys.exit() 4919 | 4920 | aa = _handle_input(call, M_raw, sys.argv[3:], _build_optparsers()[call]) 4921 | if aa is not None: 4922 | print aa 4923 | --------------------------------------------------------------------------------