diff --git a/CHANGELOG.txt b/CHANGELOG.txt index e805bf205a6..b35fe08fd85 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -22,6 +22,11 @@ Catalog/Event.write() (see obspy.nlloc and #900) * The wrap_long_string utility function is deprecated. Users may use the textwrap module which provides similar functionality. + * new plugin for CNV event format (used by VELEST) for + Catalog/Event.write() (see obspy.cnv and #905) + - obspy.cnv: + * new plugin to write CNV event files (used by VELEST) from + Catalog/Event objects. (see #905) - obspy.css: * Support for little-endian binary and ASCII files (see #881). * Support exporting Inventory objects to CSS relations. diff --git a/misc/docs/source/packages/index.rst b/misc/docs/source/packages/index.rst index 753040ee20f..ebb8e88fb6a 100644 --- a/misc/docs/source/packages/index.rst +++ b/misc/docs/source/packages/index.rst @@ -45,6 +45,7 @@ The functionality is provided through the following packages: :nosignatures: obspy.core.quakeml + obspy.cnv obspy.ndk obspy.nlloc obspy.pde diff --git a/misc/docs/source/packages/obspy.cnv.rst b/misc/docs/source/packages/obspy.cnv.rst new file mode 100644 index 00000000000..eb5b6c27d35 --- /dev/null +++ b/misc/docs/source/packages/obspy.cnv.rst @@ -0,0 +1,14 @@ +.. currentmodule:: obspy.cnv +.. automodule:: obspy.cnv + + .. comment to end block + + Modules + ------- + .. autosummary:: + :toctree: autogen + :nosignatures: + + core + + .. comment to end block diff --git a/obspy/cnv/LICENSE.txt b/obspy/cnv/LICENSE.txt new file mode 100644 index 00000000000..755013bb2e9 --- /dev/null +++ b/obspy/cnv/LICENSE.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/obspy/cnv/README.txt b/obspy/cnv/README.txt new file mode 100644 index 00000000000..c97e9c9a188 --- /dev/null +++ b/obspy/cnv/README.txt @@ -0,0 +1,26 @@ +package obspy.cnv +================= + +Copyright +--------- +GNU Lesser General Public License, Version 3 (LGPLv3) + +Copyright (c) 2014 by: + * Tobias Megies + + +Overview +-------- +CNV file format support for ObsPy + +The obspy.cnv module provides write support for CNV event summary file +format as used by VELEST program. + +ObsPy is an open-source project dedicated to provide a Python framework for +processing seismological data. It provides parsers for common file formats and +seismological signal processing routines which allow the manipulation of +seismological time series (see Beyreuther et al. 2010, Megies et al. 2011). +The goal of the ObsPy project is to facilitate rapid application development +for seismology. + +For more information visit http://www.obspy.org. diff --git a/obspy/cnv/__init__.py b/obspy/cnv/__init__.py new file mode 100644 index 00000000000..155fa2b10e1 --- /dev/null +++ b/obspy/cnv/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +""" +obspy.cnv - CNV file format support for ObsPy +============================================= + +This module provides write support for CNV event summary file +format as used by VELEST program. + +:copyright: + The ObsPy Development Team (devs@obspy.org) +:license: + GNU Lesser General Public License, Version 3 + (http://www.gnu.org/copyleft/lesser.html) +""" +from __future__ import (absolute_import, division, print_function, + unicode_literals) +from future.builtins import * # NOQA + +if __name__ == '__main__': + import doctest + doctest.testmod(exclude_empty=True) diff --git a/obspy/cnv/core.py b/obspy/cnv/core.py new file mode 100644 index 00000000000..db9e8cbe35f --- /dev/null +++ b/obspy/cnv/core.py @@ -0,0 +1,168 @@ +# -*- coding: utf-8 -*- +""" +CNV file format support for ObsPy + +:copyright: + The ObsPy Development Team (devs@obspy.org) +:license: + GNU Lesser General Public License, Version 3 + (http://www.gnu.org/copyleft/lesser.html) +""" +from __future__ import (absolute_import, division, print_function, + unicode_literals) +from future.builtins import * # NOQA @UnusedWildImport + +from bisect import bisect_right +import warnings + + +def write_CNV(catalog, filename, phase_mapping=None, ifx_list=None, + weight_mapping=None, default_weight=0): + """ + Write a :class:`~obspy.core.event.Catalog` object to CNV event summary + format (used as event/pick input by VELEST program). + + .. warning:: + This function should NOT be called directly, it registers via the + the :meth:`~obspy.core.event.Catalog.write` method of an + ObsPy :class:`~obspy.core.event.Catalog` object, call this instead. + + :type catalog: :class:`~obspy.core.event.Catalog` + :param catalog: Input catalog for CNV output.. + :type filename: str or file + :param filename: Filename to write or open file-like object. + :type phase_mapping: dict + :param phase_mapping: Mapping of phase hints to "P" or "S". CNV format only + uses a single letter phase code (either "P" or "S"). If not specified + the following default mapping is used: 'p', 'P', 'Pg', 'Pn', 'Pm' will + be mapped to "P" and 's', 'S', 'Sg', 'Sn', 'Sm' will be mapped to "S". + :type ifx_list: list of :class:`~obspy.core.event.ResourceIdentifier` + :param ifx_list: List of events for which the 'IFX' flag should be set + (used in VELEST to fix the y coordinate of the hypocenter). + :type weight_mapping: list of float + :param weight_mapping: Mapping of pick uncertainties to integer weights. + (Sorted) list of floats of boundary uncertainties. If uncertainty of + pick is lower than the first entry of the list than a weight of 0 is + assigned. If it is larger than the first entry, but smaller than the + second entry a weight of 1 is assigned, and so on. The list of + uncertainty boundaries should not contain more than 9 entries because + the integer weight is restricted to a single digit. If not specified + all picks will be output with weight `default_weight`. + :type default_weight: int + :param default_weight: Default weight to use when pick has no timing + uncertainty and thus can not be mapped using `weight_mapping` + parameter. Default weight should not be larger than 9, as the weight is + represented as a single digit. + """ + # Check phase mapping or use default one + if phase_mapping is None: + phase_mapping = {'p': "P", 'P': "P", 'Pg': "P", 'Pn': "P", 'Pm': "P", + 's': "S", 'S': "S", 'Sg': "S", 'Sn': "S", 'Sm': "S"} + else: + values = set(phase_mapping.values()) + if values.update(("P", "S")) != set(("P", "S")): + msg = ("Values of phase mapping should only be 'P' or 'S'") + raise ValueError(msg) + if ifx_list is None: + ifx_list = [] + if weight_mapping is None: + weight_mapping = [] + else: + if list(weight_mapping) != sorted(weight_mapping): + msg = ("List of floats in weight mapping must be sorted in " + "ascending order.") + raise ValueError(msg) + + out = [] + for event in catalog: + o = event.preferred_origin() or event.origins[0] + m = event.preferred_magnitude() or event.magnitudes[0] + + out_ = "%s %5.2f %7.4f%1s %8.4f%1s%7.2f%7.2f%2i\n" + cns = o.latitude >= 0 and "N" or "S" + cew = o.longitude >= 0 and "E" or "W" + if event.resource_id in ifx_list: + ifx = 1 + else: + ifx = 0 + out_ = out_ % (o.time.strftime("%y%m%d %H%M"), + o.time.second + o.time.microsecond / 1e6, + abs(o.latitude), cns, abs(o.longitude), cew, + o.depth / 1e3, m.mag, ifx) + # assemble phase info + picks = [] + for p in event.picks: + # map uncertainty to integer weight + if p.time_errors.upper_uncertainty is not None and \ + p.time_errors.lower_uncertainty is not None: + uncertainty = p.time_errors.upper_uncertainty + \ + p.time_errors.lower_uncertainty + else: + uncertainty = p.time_errors.uncertainty + if uncertainty is None: + msg = ("No pick time uncertainty, pick will be mapped to " + "default integer weight (%s).") % default_weight + warnings.warn(msg) + weight = default_weight + else: + weight = bisect_right(weight_mapping, uncertainty) + if weight > 9: + msg = ("Integer weight for pick is greater than 9. " + "This is not compatible with the single-character " + "field for pick weight in CNV format." + "Using 9 as integer weight.") + warnings.warn(msg) + weight = 9 + # map phase hint + phase = phase_mapping.get(p.phase_hint, None) + if phase is None: + msg = "Skipping pick (%s) with unmapped phase hint: %s" + msg = msg % (p.waveform_id.getSEEDString(), p.phase_hint) + warnings.warn(msg) + continue + station = p.waveform_id.station_code + if len(station) > 4: + msg = ("Station code with more than 4 characters detected. " + "Only the first 4 characters will be used in output.") + warnings.warn(msg) + station = station[:4] + dt = "%6.2f" % (p.time - o.time) + if len(dt) != 6: + msg = ("Problem with pick (%s): Calculated travel time '%s' " + "does not fit in the '%%6.2f' fixed format field. " + "Skipping this pick.") + msg = msg % (p.waveform_id.getSEEDString(), dt) + warnings.warn(msg) + continue + picks.append("".join([station.ljust(4), phase, str(weight), dt])) + while len(picks) > 6: + next_picks, picks = picks[:6], picks[6:] + out_ += "".join(next_picks) + "\n" + if picks: + out_ += "".join(picks) + "\n" + out.append(out_) + + if out: + out = "\n".join(out + [""]) + else: + msg = "No event/pick information, writing empty CNV file." + warnings.warn(msg) + + # Open filehandler or use an existing file like object. + if not hasattr(filename, "write"): + file_opened = True + fh = open(filename, "wb") + else: + file_opened = False + fh = filename + + fh.write(out.encode()) + + # Close if a file has been opened by this function. + if file_opened is True: + fh.close() + + +if __name__ == '__main__': + import doctest + doctest.testmod(exclude_empty=True) diff --git a/obspy/cnv/docs/cnv_file_format.txt b/obspy/cnv/docs/cnv_file_format.txt new file mode 100644 index 00000000000..ff248242d7c --- /dev/null +++ b/obspy/cnv/docs/cnv_file_format.txt @@ -0,0 +1,34 @@ +The input-data in *.CNV - format contains a summary-card of the format: + +(3i2.2,1x,2i2.2,1x,f5.2,1x,f7.4,a1,1x,f8.4,a1,f7.2,f7.2,i2...) +iyear,month,iday,ihr,min,sec,xlat,cns,xlon,cew,depth,emag,ifx... + (where cns is 'N' or 'S' and cew is 'E' or 'W') + +This summary card is followed by an unspecified number of lines each with six phase +data. The end of the event is marked by a blank line. For each phase the station name, +the phase type, the observation weight, and the observation time (in sec) relative to the +origin time must be specified. + +Example of earthquake data file in *.CNV format: + +90 1 1 1349 0.36 36.6745N 121.3038W 6.08 2.10 1 +BLRMP0 1.51 BCGMP0 1.59 BSCMP0 1.62 BHRMP0 2.42 BJOMP0 1.71 BEHMP0 3.22 +BSLMP0 3.33 BVYMP0 2.59 BVLMP0 3.08 HJSMP1 3.88 BJCMP0 2.91 BEMMP2 4.19 +HFPMP2 3.32 BSRMP0 3.43 HQRMP1 4.55 HLTMP1 5.39 BPIMP0 4.27 BAVMP0 5.36 + +90 1 2 310 7.67 36.6922N 121.3207W 4.32 3.20 0 +BCGMP0 1.11 BLRMP0 1.58 BHRMP0 2.23 BSCMP0 1.85 BJOMP0 2.37 BSLMP0 2.88 + + +Switch IFX: Default value=0 +For each event the switch IFX (i2, after magn.) may be set. If IFX=1, then adjustement +of this hypocenter in y-direction (to rotate y-axis see parameter ROTATE in control +file *.CMN) is inhibited. + +In the same event only one phase of the same type (P or S) is accepted for the same +station! VELEST may also use S-P travel time differences. They must be written into +a phase file of INPUTSED format (ISED=2). The phase description is "S-P". The +phase identifier is a "-" rather than P or S.Reflected waves are computed only if the +arrival-time is specified as one of a reflected wave (see below); in this case, the ray is +forced to be a reflected one and no other raypaths are computed (because a reflected +wave hardly is the first arrival...!). diff --git a/obspy/core/util/base.py b/obspy/core/util/base.py index 10c37aa9c5e..381d205b14b 100644 --- a/obspy/core/util/base.py +++ b/obspy/core/util/base.py @@ -426,6 +426,7 @@ def make_format_plugin_table(group="waveform", method="read", numspaces=4, ========= ================== ======================================== Format Required Module _`Linked Function Call` ========= ================== ======================================== + CNV :mod:`obspy.cnv` :func:`obspy.cnv.core.write_CNV` JSON :mod:`obspy.core` :func:`obspy.core.json.core.writeJSON` NLLOC_OBS :mod:`obspy.nlloc` :func:`obspy.nlloc.core.write_nlloc_obs` QUAKEML :mod:`obspy.core` :func:`obspy.core.quakeml.writeQuakeML` diff --git a/setup.py b/setup.py index 069bd779f55..90e4e3a6f84 100644 --- a/setup.py +++ b/setup.py @@ -254,6 +254,7 @@ 'NDK = obspy.ndk.core', 'NLLOC_HYP = obspy.nlloc.core', 'NLLOC_OBS = obspy.nlloc.core', + 'CNV = obspy.cnv.core', ], 'obspy.plugin.event.QUAKEML': [ 'isFormat = obspy.core.quakeml:isQuakeML', @@ -272,6 +273,9 @@ 'readFormat = obspy.zmap.core:readZmap', 'writeFormat = obspy.zmap.core:writeZmap', ], + 'obspy.plugin.event.CNV': [ + 'writeFormat = obspy.cnv.core:write_CNV', + ], 'obspy.plugin.event.NDK': [ 'isFormat = obspy.ndk.core:is_ndk', 'readFormat = obspy.ndk.core:read_ndk',