[installation] Init with inital config for global
This commit is contained in:
		
							
								
								
									
										1
									
								
								lib/libbson-1.0.so
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								lib/libbson-1.0.so
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
libbson-1.0.so.0
 | 
			
		||||
							
								
								
									
										1
									
								
								lib/libbson-1.0.so.0
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								lib/libbson-1.0.so.0
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
libbson-1.0.so.0.0.0
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								lib/libbson-1.0.so.0.0.0
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libbson-1.0.so.0.0.0
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1
									
								
								lib/libmseed.so
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								lib/libmseed.so
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
libmseed.so.2.19
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								lib/libmseed.so.2.19
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libmseed.so.2.19
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/libseiscomp_broker.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libseiscomp_broker.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1
									
								
								lib/libseiscomp_client.so
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								lib/libseiscomp_client.so
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
libseiscomp_client.so.16
 | 
			
		||||
							
								
								
									
										1
									
								
								lib/libseiscomp_client.so.16
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								lib/libseiscomp_client.so.16
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
libseiscomp_client.so.16.4.0
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								lib/libseiscomp_client.so.16.4.0
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libseiscomp_client.so.16.4.0
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/libseiscomp_config.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libseiscomp_config.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1
									
								
								lib/libseiscomp_core.so
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								lib/libseiscomp_core.so
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
libseiscomp_core.so.16
 | 
			
		||||
							
								
								
									
										1
									
								
								lib/libseiscomp_core.so.16
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								lib/libseiscomp_core.so.16
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
libseiscomp_core.so.16.4.0
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								lib/libseiscomp_core.so.16.4.0
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libseiscomp_core.so.16.4.0
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/libseiscomp_daplugin.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libseiscomp_daplugin.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/libseiscomp_datamodel_sm.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libseiscomp_datamodel_sm.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/libseiscomp_evplugin.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libseiscomp_evplugin.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/libseiscomp_gempa_caps_auth.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libseiscomp_gempa_caps_auth.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1
									
								
								lib/libseiscomp_gempaasio.so
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								lib/libseiscomp_gempaasio.so
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
libseiscomp_gempaasio.so.3
 | 
			
		||||
							
								
								
									
										1
									
								
								lib/libseiscomp_gempaasio.so.3
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								lib/libseiscomp_gempaasio.so.3
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
libseiscomp_gempaasio.so.3.0.0
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								lib/libseiscomp_gempaasio.so.3.0.0
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libseiscomp_gempaasio.so.3.0.0
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1
									
								
								lib/libseiscomp_gempagui.so
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								lib/libseiscomp_gempagui.so
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
libseiscomp_gempagui.so.5
 | 
			
		||||
							
								
								
									
										1
									
								
								lib/libseiscomp_gempagui.so.5
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								lib/libseiscomp_gempagui.so.5
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
libseiscomp_gempagui.so.5.4.1
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								lib/libseiscomp_gempagui.so.5.4.1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libseiscomp_gempagui.so.5.4.1
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1
									
								
								lib/libseiscomp_gempautils.so
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								lib/libseiscomp_gempautils.so
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
libseiscomp_gempautils.so.4
 | 
			
		||||
							
								
								
									
										1
									
								
								lib/libseiscomp_gempautils.so.4
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								lib/libseiscomp_gempautils.so.4
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
libseiscomp_gempautils.so.4.7.0
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								lib/libseiscomp_gempautils.so.4.7.0
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libseiscomp_gempautils.so.4.7.0
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/libseiscomp_mplugin.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libseiscomp_mplugin.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/libseiscomp_qcplugin.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libseiscomp_qcplugin.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1
									
								
								lib/libseiscomp_qt.so
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								lib/libseiscomp_qt.so
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
libseiscomp_qt.so.16
 | 
			
		||||
							
								
								
									
										1
									
								
								lib/libseiscomp_qt.so.16
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								lib/libseiscomp_qt.so.16
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
libseiscomp_qt.so.16.4.0
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								lib/libseiscomp_qt.so.16.4.0
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libseiscomp_qt.so.16.4.0
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/libseiscomp_unittest.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/libseiscomp_unittest.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										10
									
								
								lib/pkgconfig/libbson-1.0.pc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								lib/pkgconfig/libbson-1.0.pc
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
prefix=/home/sysop/gitlocal/bmp/6-release/seiscomp/build-gpkg/deploy/seiscomp
 | 
			
		||||
exec_prefix=${prefix}
 | 
			
		||||
libdir=${prefix}/lib
 | 
			
		||||
includedir=${exec_prefix}/include
 | 
			
		||||
 | 
			
		||||
Name: libbson
 | 
			
		||||
Description: The libbson BSON serialization library.
 | 
			
		||||
Version: 1.14.0
 | 
			
		||||
Libs: -L${libdir} -lbson-1.0
 | 
			
		||||
Cflags: -I${includedir}/libbson-1.0
 | 
			
		||||
							
								
								
									
										1409
									
								
								lib/python/gempa/CAPS.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1409
									
								
								lib/python/gempa/CAPS.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/python/gempa/CAPS.pyo
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/python/gempa/CAPS.pyo
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										154
									
								
								lib/python/gempa/Processing.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								lib/python/gempa/Processing.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,154 @@
 | 
			
		||||
# This file was automatically generated by SWIG (https://www.swig.org).
 | 
			
		||||
# Version 4.3.0
 | 
			
		||||
#
 | 
			
		||||
# Do not make changes to this file unless you know what you are doing - modify
 | 
			
		||||
# the SWIG interface file instead.
 | 
			
		||||
 | 
			
		||||
from sys import version_info as _swig_python_version_info
 | 
			
		||||
# Import the low-level C/C++ module
 | 
			
		||||
if __package__ or "." in __name__:
 | 
			
		||||
    from . import _gProcessing
 | 
			
		||||
else:
 | 
			
		||||
    import _gProcessing
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import builtins as __builtin__
 | 
			
		||||
except ImportError:
 | 
			
		||||
    import __builtin__
 | 
			
		||||
 | 
			
		||||
def _swig_repr(self):
 | 
			
		||||
    try:
 | 
			
		||||
        strthis = "proxy of " + self.this.__repr__()
 | 
			
		||||
    except __builtin__.Exception:
 | 
			
		||||
        strthis = ""
 | 
			
		||||
    return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _swig_setattr_nondynamic_instance_variable(set):
 | 
			
		||||
    def set_instance_attr(self, name, value):
 | 
			
		||||
        if name == "this":
 | 
			
		||||
            set(self, name, value)
 | 
			
		||||
        elif name == "thisown":
 | 
			
		||||
            self.this.own(value)
 | 
			
		||||
        elif hasattr(self, name) and isinstance(getattr(type(self), name), property):
 | 
			
		||||
            set(self, name, value)
 | 
			
		||||
        else:
 | 
			
		||||
            raise AttributeError("You cannot add instance attributes to %s" % self)
 | 
			
		||||
    return set_instance_attr
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _swig_setattr_nondynamic_class_variable(set):
 | 
			
		||||
    def set_class_attr(cls, name, value):
 | 
			
		||||
        if hasattr(cls, name) and not isinstance(getattr(cls, name), property):
 | 
			
		||||
            set(cls, name, value)
 | 
			
		||||
        else:
 | 
			
		||||
            raise AttributeError("You cannot add class attributes to %s" % cls)
 | 
			
		||||
    return set_class_attr
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _swig_add_metaclass(metaclass):
 | 
			
		||||
    """Class decorator for adding a metaclass to a SWIG wrapped class - a slimmed down version of six.add_metaclass"""
 | 
			
		||||
    def wrapper(cls):
 | 
			
		||||
        return metaclass(cls.__name__, cls.__bases__, cls.__dict__.copy())
 | 
			
		||||
    return wrapper
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _SwigNonDynamicMeta(type):
 | 
			
		||||
    """Meta class to enforce nondynamic attributes (no new attributes) for a class"""
 | 
			
		||||
    __setattr__ = _swig_setattr_nondynamic_class_variable(type.__setattr__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Ecef2Enu(object):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
 | 
			
		||||
    def __init__(self, lat, lon, h):
 | 
			
		||||
        _gProcessing.Ecef2Enu_swiginit(self, _gProcessing.new_Ecef2Enu(lat, lon, h))
 | 
			
		||||
 | 
			
		||||
    def convert(self, x, y, z):
 | 
			
		||||
        return _gProcessing.Ecef2Enu_convert(self, x, y, z)
 | 
			
		||||
    __swig_destroy__ = _gProcessing.delete_Ecef2Enu
 | 
			
		||||
 | 
			
		||||
# Register Ecef2Enu in _gProcessing:
 | 
			
		||||
_gProcessing.Ecef2Enu_swigregister(Ecef2Enu)
 | 
			
		||||
class Enu2Ecef(object):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
 | 
			
		||||
    def __init__(self, lat, lon, h):
 | 
			
		||||
        _gProcessing.Enu2Ecef_swiginit(self, _gProcessing.new_Enu2Ecef(lat, lon, h))
 | 
			
		||||
 | 
			
		||||
    def convert(self, e, n, u):
 | 
			
		||||
        return _gProcessing.Enu2Ecef_convert(self, e, n, u)
 | 
			
		||||
    __swig_destroy__ = _gProcessing.delete_Enu2Ecef
 | 
			
		||||
 | 
			
		||||
# Register Enu2Ecef in _gProcessing:
 | 
			
		||||
_gProcessing.Enu2Ecef_swigregister(Enu2Ecef)
 | 
			
		||||
 | 
			
		||||
def geodetic2ecef(lat, lon, h):
 | 
			
		||||
    return _gProcessing.geodetic2ecef(lat, lon, h)
 | 
			
		||||
 | 
			
		||||
def distance(strike1, dip1, rake1, strike2, dip2, rake2, scaleX=1.0, scaleY=1.0, scaleZ=1.0):
 | 
			
		||||
    return _gProcessing.distance(strike1, dip1, rake1, strike2, dip2, rake2, scaleX, scaleY, scaleZ)
 | 
			
		||||
 | 
			
		||||
def rotAngleNP(strike1, dip1, rake1, strike2, dip2, rake2):
 | 
			
		||||
    return _gProcessing.rotAngleNP(strike1, dip1, rake1, strike2, dip2, rake2)
 | 
			
		||||
 | 
			
		||||
def rotAngleMT(strike1, dip1, rake1, strike2, dip2, rake2):
 | 
			
		||||
    return _gProcessing.rotAngleMT(strike1, dip1, rake1, strike2, dip2, rake2)
 | 
			
		||||
 | 
			
		||||
def otherNodalPlane(inStrike, inDip, inRake):
 | 
			
		||||
    return _gProcessing.otherNodalPlane(inStrike, inDip, inRake)
 | 
			
		||||
 | 
			
		||||
def nodalPlane2Tensor(strike, dip, rake):
 | 
			
		||||
    return _gProcessing.nodalPlane2Tensor(strike, dip, rake)
 | 
			
		||||
class Vector3D(object):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args):
 | 
			
		||||
        _gProcessing.Vector3D_swiginit(self, _gProcessing.new_Vector3D(*args))
 | 
			
		||||
 | 
			
		||||
    def length(self):
 | 
			
		||||
        return _gProcessing.Vector3D_length(self)
 | 
			
		||||
 | 
			
		||||
    def dot(self, v):
 | 
			
		||||
        return _gProcessing.Vector3D_dot(self, v)
 | 
			
		||||
 | 
			
		||||
    def cross(self, a, b):
 | 
			
		||||
        return _gProcessing.Vector3D_cross(self, a, b)
 | 
			
		||||
 | 
			
		||||
    def normalize(self):
 | 
			
		||||
        return _gProcessing.Vector3D_normalize(self)
 | 
			
		||||
 | 
			
		||||
    def __imul__(self, scale):
 | 
			
		||||
        return _gProcessing.Vector3D___imul__(self, scale)
 | 
			
		||||
 | 
			
		||||
    def __mul__(self, *args):
 | 
			
		||||
        return _gProcessing.Vector3D___mul__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __iadd__(self, other):
 | 
			
		||||
        return _gProcessing.Vector3D___iadd__(self, other)
 | 
			
		||||
 | 
			
		||||
    def __isub__(self, other):
 | 
			
		||||
        return _gProcessing.Vector3D___isub__(self, other)
 | 
			
		||||
 | 
			
		||||
    def __add__(self, other):
 | 
			
		||||
        return _gProcessing.Vector3D___add__(self, other)
 | 
			
		||||
 | 
			
		||||
    def __sub__(self, other):
 | 
			
		||||
        return _gProcessing.Vector3D___sub__(self, other)
 | 
			
		||||
 | 
			
		||||
    def fromAngles(self, radAzimuth, radDip):
 | 
			
		||||
        return _gProcessing.Vector3D_fromAngles(self, radAzimuth, radDip)
 | 
			
		||||
 | 
			
		||||
    def toAngles(self, radAzimuth, radDip):
 | 
			
		||||
        return _gProcessing.Vector3D_toAngles(self, radAzimuth, radDip)
 | 
			
		||||
    x = property(_gProcessing.Vector3D_x_get, _gProcessing.Vector3D_x_set)
 | 
			
		||||
    y = property(_gProcessing.Vector3D_y_get, _gProcessing.Vector3D_y_set)
 | 
			
		||||
    z = property(_gProcessing.Vector3D_z_get, _gProcessing.Vector3D_z_set)
 | 
			
		||||
    __swig_destroy__ = _gProcessing.delete_Vector3D
 | 
			
		||||
 | 
			
		||||
# Register Vector3D in _gProcessing:
 | 
			
		||||
_gProcessing.Vector3D_swigregister(Vector3D)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								lib/python/gempa/Processing.pyo
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/python/gempa/Processing.pyo
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										0
									
								
								lib/python/gempa/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								lib/python/gempa/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/python/gempa/_gCAPS.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/python/gempa/_gCAPS.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/python/gempa/_gProcessing.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/python/gempa/_gProcessing.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										0
									
								
								lib/python/licsar2caps/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								lib/python/licsar2caps/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										104
									
								
								lib/python/licsar2caps/journal.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								lib/python/licsar2caps/journal.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,104 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
 | 
			
		||||
############################################################################
 | 
			
		||||
# Copyright (C) 2024 by gempa GmbH                                         #
 | 
			
		||||
#                                                                          #
 | 
			
		||||
# All Rights Reserved.                                                     #
 | 
			
		||||
#                                                                          #
 | 
			
		||||
# NOTICE: All information contained herein is, and remains                 #
 | 
			
		||||
# the property of gempa GmbH and its suppliers, if any. The intellectual   #
 | 
			
		||||
# and technical concepts contained herein are proprietary to gempa GmbH    #
 | 
			
		||||
# and its suppliers.                                                       #
 | 
			
		||||
# Dissemination of this information or reproduction of this material       #
 | 
			
		||||
# is strictly forbidden unless prior written permission is obtained        #
 | 
			
		||||
# from gempa GmbH.                                                         #
 | 
			
		||||
############################################################################
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from seiscomp import logging
 | 
			
		||||
 | 
			
		||||
from gempa import CAPS
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class JournalItem:
 | 
			
		||||
    def __init__(self, startTime=None, endTime=None):
 | 
			
		||||
        self.startTime = startTime
 | 
			
		||||
        self.endTime = endTime
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Journal:
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.items = {}
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def get(self, streamID):
 | 
			
		||||
        return self.items.get(streamID)
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def read(self, filename):
 | 
			
		||||
        try:
 | 
			
		||||
            f = open(filename, "r", encoding="UTF-8")
 | 
			
		||||
        except Exception as err:
 | 
			
		||||
            logging.error(f"Journal: Could not open file: {err}")
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            lineNo = 0
 | 
			
		||||
            for line in f:
 | 
			
		||||
                line = line.strip()
 | 
			
		||||
                if line.startswith("#"):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                try:
 | 
			
		||||
                    stationID, strStartTime, strEndTime = line.split(" ")
 | 
			
		||||
                except ValueError:
 | 
			
		||||
                    logging.error(
 | 
			
		||||
                        f"Journal: Invalid line format in line {lineNo}"
 | 
			
		||||
                    )
 | 
			
		||||
                    return False
 | 
			
		||||
 | 
			
		||||
                item = JournalItem()
 | 
			
		||||
 | 
			
		||||
                item.startTime = CAPS.Time.FromString(strStartTime, "%FT%T.%Z")
 | 
			
		||||
                item.endTime = CAPS.Time.FromString(strEndTime, "%FT%T.%Z")
 | 
			
		||||
 | 
			
		||||
                self.items[stationID] = item
 | 
			
		||||
 | 
			
		||||
                lineNo += 1
 | 
			
		||||
        except IOError as err:
 | 
			
		||||
            logging.error(f"Journal: Could not read journal from file: {err}")
 | 
			
		||||
        finally:
 | 
			
		||||
            f.close()
 | 
			
		||||
 | 
			
		||||
        logging.info("Recovered journal")
 | 
			
		||||
        for k, v in self.items.items():
 | 
			
		||||
            logging.info(f"  + {k} {v.startTime.iso()} ~ {v.endTime.iso()}")
 | 
			
		||||
 | 
			
		||||
        logging.info("End")
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def write(self, filename):
 | 
			
		||||
        path = os.path.dirname(filename)
 | 
			
		||||
        if not path:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if not os.path.exists(path):
 | 
			
		||||
            try:
 | 
			
		||||
                os.makedirs(path)
 | 
			
		||||
            except Exception as err:
 | 
			
		||||
                logging.error(f"Journal: Could not create directory: {err}")
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            with open(filename, "w", encoding="UTF-8") as f:
 | 
			
		||||
                for k, v in self.items.items():
 | 
			
		||||
                    f.write(f"{k} {v.startTime.iso()} {v.endTime.iso()}\n")
 | 
			
		||||
        except Exception as err:
 | 
			
		||||
            logging.error(f"Journal: Faild to write journal: {err}")
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
							
								
								
									
										127
									
								
								lib/python/licsar2caps/streammap.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								lib/python/licsar2caps/streammap.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,127 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
 | 
			
		||||
############################################################################
 | 
			
		||||
# Copyright (C) 2024 by gempa GmbH                                         #
 | 
			
		||||
#                                                                          #
 | 
			
		||||
# All Rights Reserved.                                                     #
 | 
			
		||||
#                                                                          #
 | 
			
		||||
# NOTICE: All information contained herein is, and remains                 #
 | 
			
		||||
# the property of gempa GmbH and its suppliers, if any. The intellectual   #
 | 
			
		||||
# and technical concepts contained herein are proprietary to gempa GmbH    #
 | 
			
		||||
# and its suppliers.                                                       #
 | 
			
		||||
# Dissemination of this information or reproduction of this material       #
 | 
			
		||||
# is strictly forbidden unless prior written permission is obtained        #
 | 
			
		||||
# from gempa GmbH.                                                         #
 | 
			
		||||
############################################################################
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from seiscomp import logging
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StreamMapItem:
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.networkCode = ""
 | 
			
		||||
        self.stationCode = ""
 | 
			
		||||
        self.locationCode = ""
 | 
			
		||||
        self.stationID = ""
 | 
			
		||||
        self.baseCode = None
 | 
			
		||||
        self.folder = None
 | 
			
		||||
        self.startTime = None
 | 
			
		||||
        self.endTime = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StreamMap:
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.items = {}
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def get(self, streamID):
 | 
			
		||||
        return self.items.get(streamID)
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def read(self, filename):
 | 
			
		||||
        try:
 | 
			
		||||
            f = open(filename, "r", encoding="UTF-8")
 | 
			
		||||
        except Exception as err:
 | 
			
		||||
            logging.error(f"Stream map: Could not open file: {err}")
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            lineNo = -1
 | 
			
		||||
            for line in f:
 | 
			
		||||
                lineNo += 1
 | 
			
		||||
                line = line.strip()
 | 
			
		||||
                if line.startswith("#"):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                if len(line) == 0:
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                folder = line.strip()
 | 
			
		||||
 | 
			
		||||
                toks = folder.split("_")
 | 
			
		||||
                tokCount = len(toks)
 | 
			
		||||
                if tokCount != 3:
 | 
			
		||||
                    logging.error(
 | 
			
		||||
                        f"Stream map: Invalid stream ID in line {lineNo}"
 | 
			
		||||
                    )
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                item = StreamMapItem()
 | 
			
		||||
                item.networkCode = toks[0]
 | 
			
		||||
                item.stationCode = toks[1]
 | 
			
		||||
                item.locationCode = toks[2]
 | 
			
		||||
                item.baseCode = str(int(item.networkCode[0:3]))
 | 
			
		||||
                item.folder = folder
 | 
			
		||||
                item.stationID = (
 | 
			
		||||
                    item.networkCode
 | 
			
		||||
                    + "."
 | 
			
		||||
                    + item.stationCode
 | 
			
		||||
                    + "."
 | 
			
		||||
                    + item.locationCode
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                self.items[item.stationID] = item
 | 
			
		||||
        except IOError as err:
 | 
			
		||||
            logging.error(
 | 
			
		||||
                f"Stream map: Could not read stream map from file: {err}"
 | 
			
		||||
            )
 | 
			
		||||
        finally:
 | 
			
		||||
            f.close()
 | 
			
		||||
 | 
			
		||||
        if len(self.items) == 0:
 | 
			
		||||
            logging.info("No streams configured: Nothing todo")
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        logging.info("Configured stations")
 | 
			
		||||
        for k, _v in self.items.items():
 | 
			
		||||
            logging.info(f"  + {k}")
 | 
			
		||||
 | 
			
		||||
        logging.info("End")
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def write(self, filename):
 | 
			
		||||
        path = os.path.dirname(filename)
 | 
			
		||||
        if not path:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if not os.path.exists(path):
 | 
			
		||||
            try:
 | 
			
		||||
                os.makedirs(path)
 | 
			
		||||
            except Exception as err:
 | 
			
		||||
                logging.error(f"Stream map: Could not create directory: {err}")
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            with open(filename, "w", encoding="UTF-8") as f:
 | 
			
		||||
                for k, v in self.items.items():
 | 
			
		||||
                    f.write(f"{k} {v.startTime.iso()} {v.endTime.iso()}\n")
 | 
			
		||||
        except Exception as err:
 | 
			
		||||
            logging.error(f"Stream map: Could not open file: {err}")
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
							
								
								
									
										36
									
								
								lib/python/licsar2caps/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								lib/python/licsar2caps/utils.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
 | 
			
		||||
############################################################################
 | 
			
		||||
# Copyright (C) 2024 by gempa GmbH                                         #
 | 
			
		||||
#                                                                          #
 | 
			
		||||
# All Rights Reserved.                                                     #
 | 
			
		||||
#                                                                          #
 | 
			
		||||
# NOTICE: All information contained herein is, and remains                 #
 | 
			
		||||
# the property of gempa GmbH and its suppliers, if any. The intellectual   #
 | 
			
		||||
# and technical concepts contained herein are proprietary to gempa GmbH    #
 | 
			
		||||
# and its suppliers.                                                       #
 | 
			
		||||
# Dissemination of this information or reproduction of this material       #
 | 
			
		||||
# is strictly forbidden unless prior written permission is obtained        #
 | 
			
		||||
# from gempa GmbH.                                                         #
 | 
			
		||||
############################################################################
 | 
			
		||||
 | 
			
		||||
import numpy as np
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from gempa import CAPS
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def calculateAbsPerc(grid, percentile=99.9):
 | 
			
		||||
    grid_array = np.array(grid)
 | 
			
		||||
    result = np.percentile(np.abs(grid_array), percentile)
 | 
			
		||||
    return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def parseTime(s):
 | 
			
		||||
    formats = ["%F", "%F %T", "%F %T.%Z", "%FT%T", "%FT%T.%Z"]
 | 
			
		||||
    for fmt in formats:
 | 
			
		||||
        time = CAPS.Time.FromString(s, fmt)
 | 
			
		||||
        if time.valid():
 | 
			
		||||
            return time
 | 
			
		||||
 | 
			
		||||
    return None
 | 
			
		||||
							
								
								
									
										0
									
								
								lib/python/nettab/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								lib/python/nettab/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										366
									
								
								lib/python/nettab/basesc3.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										366
									
								
								lib/python/nettab/basesc3.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,366 @@
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
import seiscomp.datamodel, seiscomp.core, seiscomp.config
 | 
			
		||||
from .helpers import parsers
 | 
			
		||||
import datetime
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class sc3(object):
 | 
			
		||||
    def _fillSc3(self, obj, att):
 | 
			
		||||
        commentNum = 0
 | 
			
		||||
        for (k, p) in att.items():
 | 
			
		||||
            try:
 | 
			
		||||
                if k == 'Comment':
 | 
			
		||||
                    # print('DEBUG: Adding comment', p)
 | 
			
		||||
                    if p.startswith('Grant'):
 | 
			
		||||
                        # 2020: These belong in DOI metadata, not here.
 | 
			
		||||
                        continue
 | 
			
		||||
 | 
			
		||||
                    c = seiscomp.datamodel.Comment()
 | 
			
		||||
                    c.setText(p)
 | 
			
		||||
                    c.setId(str(commentNum))
 | 
			
		||||
                    commentNum += 1
 | 
			
		||||
                    obj.add(c)
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                if k == 'Pid':
 | 
			
		||||
                    # print('DEBUG: Adding Pid as comment', p)
 | 
			
		||||
                    c = seiscomp.datamodel.Comment()
 | 
			
		||||
                    (typ, val) = p.split(':', 1)
 | 
			
		||||
                    s = '{"type":"%s", "value":"%s"}' % (typ.upper(), val)
 | 
			
		||||
                    c.setText(s)
 | 
			
		||||
                    c.setId('FDSNXML:Identifier/' + str(commentNum))
 | 
			
		||||
                    commentNum += 1
 | 
			
		||||
                    obj.add(c)
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                w = 'set' + k
 | 
			
		||||
                p = self.sc3Valid['attributes'][k]['validator'](p)
 | 
			
		||||
                getattr(obj, w)(p)
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                print("[Error] %s = %s (%s)" % (k, p, e),
 | 
			
		||||
                      file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def getBool(val):
 | 
			
		||||
        if val == "True" or val == 1:
 | 
			
		||||
            return True
 | 
			
		||||
        elif val == "False" or val == 0:
 | 
			
		||||
            return False
 | 
			
		||||
        else:
 | 
			
		||||
            raise Exception("Invalid Boolean Value")
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def getString(data):
 | 
			
		||||
        return data.strip()
 | 
			
		||||
    
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def getRealArray(data):
 | 
			
		||||
        RA = seiscomp.datamodel.RealArray()
 | 
			
		||||
        for r in map(float, data):
 | 
			
		||||
            RA.content().push_back(r)
 | 
			
		||||
        return RA
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def getComplexArray(data):
 | 
			
		||||
        CA = seiscomp.datamodel.ComplexArray()
 | 
			
		||||
        for (r,i) in data:
 | 
			
		||||
            CA.content().push_back(complex(float(r),float(i)))
 | 
			
		||||
        return CA
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def getDate(value):
 | 
			
		||||
        if isinstance(value, datetime.datetime):
 | 
			
		||||
            return seiscomp.core.Time(*(value.timetuple()[:6]))
 | 
			
		||||
        elif isinstance(value, str):
 | 
			
		||||
            value = parsers.parseDate(value)
 | 
			
		||||
            return seiscomp.core.Time(*(value.timetuple()[:6]))
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def getBlob(value):
 | 
			
		||||
        b = seiscomp.datamodel.Blob()
 | 
			
		||||
        b.setContent(value)
 | 
			
		||||
        return b
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def getStationGroupType(val):
 | 
			
		||||
        if val == "ARRAY":
 | 
			
		||||
            return seiscomp.datamodel.ARRAY
 | 
			
		||||
        elif val == "DEPLOYMENT":
 | 
			
		||||
            return seiscomp.datamodel.DEPLOYMENT
 | 
			
		||||
        else:
 | 
			
		||||
            raise Exception("Invalid station group type")
 | 
			
		||||
 | 
			
		||||
    @staticmethod    
 | 
			
		||||
    def _findValidOnes(mode):
 | 
			
		||||
        valid = {
 | 
			
		||||
        'dataloggerCalibration': {
 | 
			
		||||
            'creator': seiscomp.datamodel.DataloggerCalibration,
 | 
			
		||||
            'attributes': {
 | 
			
		||||
                'SerialNumber':           { 'validator': sc3.getString },
 | 
			
		||||
                'Channel':                { 'validator': int },
 | 
			
		||||
                'Start':                  { 'validator': sc3.getDate },
 | 
			
		||||
                'End':                    { 'validator': sc3.getDate },
 | 
			
		||||
                'Gain':                   { 'validator': float },
 | 
			
		||||
                'GainFrequency':          { 'validator': float },
 | 
			
		||||
                'Remark':                 { 'validator': sc3.getBlob }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        'sensorCalibration': {
 | 
			
		||||
            'creator': seiscomp.datamodel.SensorCalibration,
 | 
			
		||||
            'attributes': {
 | 
			
		||||
                'SerialNumber':           { 'validator': sc3.getString },
 | 
			
		||||
                'Channel':                { 'validator': int },
 | 
			
		||||
                'Start':                  { 'validator': sc3.getDate },
 | 
			
		||||
                'End':                    { 'validator': sc3.getDate },
 | 
			
		||||
                'Gain':                   { 'validator': float },
 | 
			
		||||
                'GainFrequency':          { 'validator': float },
 | 
			
		||||
                'Remark':                 { 'validator': sc3.getBlob }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        'channel': {
 | 
			
		||||
            'creator': seiscomp.datamodel.Stream_Create,
 | 
			
		||||
            'attributes': {
 | 
			
		||||
                'Code':                   { 'validator': sc3.getString },
 | 
			
		||||
                'Start':                  { 'validator': sc3.getDate },
 | 
			
		||||
                'End':                    { 'validator': sc3.getDate },
 | 
			
		||||
                'Datalogger':             { 'validator': sc3.getString },
 | 
			
		||||
                'DataloggerSerialNumber': { 'validator': sc3.getString },
 | 
			
		||||
                'DataloggerChannel':      { 'validator': int },
 | 
			
		||||
                'Sensor':                 { 'validator': sc3.getString },
 | 
			
		||||
                'SensorSerialNumber':     { 'validator': sc3.getString },
 | 
			
		||||
                'SensorChannel':          { 'validator': int },
 | 
			
		||||
                'ClockSerialNumber':      { 'validator': sc3.getString },
 | 
			
		||||
                'SampleRateNumerator':    { 'validator': int },
 | 
			
		||||
                'SampleRateDenominator':  { 'validator': int },
 | 
			
		||||
                'Depth':                  { 'validator': float },
 | 
			
		||||
                'Azimuth':                { 'validator': float },
 | 
			
		||||
                'Dip':                    { 'validator': float },
 | 
			
		||||
                'Gain':                   { 'validator': float },
 | 
			
		||||
                'GainFrequency':          { 'validator': float },
 | 
			
		||||
                'GainUnit':               { 'validator': sc3.getString },
 | 
			
		||||
                'Format':                 { 'validator': sc3.getString },
 | 
			
		||||
                'Flags':                  { 'validator': sc3.getString },
 | 
			
		||||
                'Restricted':             { 'validator': sc3.getBool },
 | 
			
		||||
                'Shared':                 { 'validator': sc3.getBool }
 | 
			
		||||
            } 
 | 
			
		||||
        },
 | 
			
		||||
        'location': {
 | 
			
		||||
            'creator': seiscomp.datamodel.SensorLocation_Create,
 | 
			
		||||
            'attributes': {
 | 
			
		||||
                'Code':                   { 'validator': sc3.getString },
 | 
			
		||||
                'Start':                  { 'validator': sc3.getDate },
 | 
			
		||||
                'End':                    { 'validator': sc3.getDate },
 | 
			
		||||
                "Latitude":               { 'validator': float },
 | 
			
		||||
                "Longitude":              { 'validator': float },
 | 
			
		||||
                "Elevation":              { 'validator': float }
 | 
			
		||||
                }
 | 
			
		||||
        },
 | 
			
		||||
        'station': {
 | 
			
		||||
            'creator': seiscomp.datamodel.Station_Create,
 | 
			
		||||
            'attributes': {
 | 
			
		||||
                'Code':                   { 'validator': sc3.getString },
 | 
			
		||||
                'Start':                  { 'validator': sc3.getDate },
 | 
			
		||||
                'End':                    { 'validator': sc3.getDate },
 | 
			
		||||
                'Description':            { 'validator': sc3.getString },
 | 
			
		||||
                'Latitude':               { 'validator': float },
 | 
			
		||||
                'Longitude':              { 'validator': float },
 | 
			
		||||
                'Elevation':              { 'validator': float },
 | 
			
		||||
                'Place':                  { 'validator': sc3.getString },
 | 
			
		||||
                'Country':                { 'validator': sc3.getString },
 | 
			
		||||
                'Affiliation':            { 'validator': sc3.getString },
 | 
			
		||||
                'Type':                   { 'validator': sc3.getString },
 | 
			
		||||
                'ArchiveNetworkCode':     { 'validator': sc3.getString },
 | 
			
		||||
                'Archive':                { 'validator': sc3.getString },
 | 
			
		||||
                'Restricted':             { 'validator': sc3.getBool },
 | 
			
		||||
                'Shared':                 { 'validator': sc3.getBool },
 | 
			
		||||
                'Remark':                 { 'validator': sc3.getBlob }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        'network': {
 | 
			
		||||
            'creator': seiscomp.datamodel.Network_Create,
 | 
			
		||||
            'attributes': {
 | 
			
		||||
                'Code':                   { 'validator': sc3.getString },
 | 
			
		||||
                'Start':                  { 'validator': sc3.getDate },
 | 
			
		||||
                'End':                    { 'validator': sc3.getDate },
 | 
			
		||||
                'Description':            { 'validator': sc3.getString },
 | 
			
		||||
                'Institutions':           { 'validator': sc3.getString },
 | 
			
		||||
                'Region':                 { 'validator': sc3.getString },
 | 
			
		||||
                'Type':                   { 'validator': sc3.getString },
 | 
			
		||||
                'NetClass':               { 'validator': sc3.getString },
 | 
			
		||||
                'Archive':                { 'validator': sc3.getString },
 | 
			
		||||
                'Comment':                { 'validator': sc3.getString },
 | 
			
		||||
                'Pid':                    { 'validator': sc3.getBlob },
 | 
			
		||||
                'Restricted':             { 'validator': sc3.getBool },
 | 
			
		||||
                'Shared':                 { 'validator': sc3.getBool },
 | 
			
		||||
                'Remark':                 { 'validator': sc3.getBlob }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        'stationGroup': {
 | 
			
		||||
            'creator': seiscomp.datamodel.StationGroup_Create,
 | 
			
		||||
            'attributes': {
 | 
			
		||||
                'Code':                   { 'validator': sc3.getString },
 | 
			
		||||
                'Start':                  { 'validator': sc3.getDate },
 | 
			
		||||
                'End':                    { 'validator': sc3.getDate },
 | 
			
		||||
                'Description':            { 'validator': sc3.getString },
 | 
			
		||||
                'Type':                   { 'validator': sc3.getStationGroupType },
 | 
			
		||||
                'Latitude':               { 'validator': float },
 | 
			
		||||
                'Longitude':              { 'validator': float },
 | 
			
		||||
                'Elevation':              { 'validator': float },
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        'stationReference': {
 | 
			
		||||
            'creator': seiscomp.datamodel.StationReference,
 | 
			
		||||
            'attributes': {
 | 
			
		||||
                'StationID':              { 'validator': sc3.getString },
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        'datalogger': {
 | 
			
		||||
            'creator': seiscomp.datamodel.Datalogger_Create,
 | 
			
		||||
            'attributes': {
 | 
			
		||||
                'Name':                   { 'validator': sc3.getString },
 | 
			
		||||
                'Description':            { 'validator': sc3.getString },
 | 
			
		||||
                'DigitizerModel':         { 'validator': sc3.getString },
 | 
			
		||||
                'DigitizerManufacturer':  { 'validator': sc3.getString },
 | 
			
		||||
                'RecorderModel':          { 'validator': sc3.getString },
 | 
			
		||||
                'RecorderManufacturer':   { 'validator': sc3.getString },
 | 
			
		||||
                'ClockModel':             { 'validator': sc3.getString },
 | 
			
		||||
                'ClockManufacturer':      { 'validator': sc3.getString },
 | 
			
		||||
                'ClockType':              { 'validator': sc3.getString },
 | 
			
		||||
                'Gain':                   { 'validator': float },
 | 
			
		||||
                'MaxClockDrift':          { 'validator': float },
 | 
			
		||||
                'Remark':                 { 'validator': sc3.getBlob }
 | 
			
		||||
            }
 | 
			
		||||
        }, 
 | 
			
		||||
        'decimation': {
 | 
			
		||||
            'creator': seiscomp.datamodel.Decimation,
 | 
			
		||||
            'attributes': {
 | 
			
		||||
                'SampleRateNumerator':    { 'validator': int },
 | 
			
		||||
                'SampleRateDenominator':  { 'validator': int },
 | 
			
		||||
                'AnalogueFilterChain':    { 'validator': sc3.getBlob },
 | 
			
		||||
                'DigitalFilterChain':     { 'validator': sc3.getBlob }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        'fir': {
 | 
			
		||||
            'creator': seiscomp.datamodel.ResponseFIR_Create,
 | 
			
		||||
            'attributes': {
 | 
			
		||||
                "Name":                   { 'validator': sc3.getString },
 | 
			
		||||
                "Gain":                   { 'validator': float },
 | 
			
		||||
                "DecimationFactor":       { 'validator': int },
 | 
			
		||||
                "Delay":                  { 'validator': float },
 | 
			
		||||
                "Correction":             { 'validator': float },
 | 
			
		||||
                "NumberOfCoefficients":   { 'validator': int },
 | 
			
		||||
                "Symmetry":               { 'validator': sc3.getString },
 | 
			
		||||
                "Coefficients":           { 'validator': sc3.getRealArray },
 | 
			
		||||
                "Remarks":                { 'validator': sc3.getBlob }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        'paz': {
 | 
			
		||||
            'creator': seiscomp.datamodel.ResponsePAZ_Create,
 | 
			
		||||
            'attributes': {
 | 
			
		||||
                'Name':                   { 'validator': sc3.getString },
 | 
			
		||||
                'Description':            { 'validator': sc3.getString },
 | 
			
		||||
                'Type':                   { 'validator': sc3.getString },
 | 
			
		||||
                'Gain':                   { 'validator': float },
 | 
			
		||||
                'GainFrequency':          { 'validator': float },
 | 
			
		||||
                'NormalizationFactor':    { 'validator': float },
 | 
			
		||||
                'NormalizationFrequency': { 'validator': float },
 | 
			
		||||
                'NumberOfZeros':          { 'validator': int },
 | 
			
		||||
                'NumberOfPoles':          { 'validator': int },
 | 
			
		||||
                'Zeros':                  { 'validator': sc3.getComplexArray },
 | 
			
		||||
                'Poles':                  { 'validator': sc3.getComplexArray },
 | 
			
		||||
                'Remark':                 { 'validator': sc3.getBlob }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        'sensor': {
 | 
			
		||||
            'creator': seiscomp.datamodel.Sensor_Create,
 | 
			
		||||
            'attributes': {
 | 
			
		||||
                'Name':                   { 'validator': sc3.getString },
 | 
			
		||||
                'Description':            { 'validator': sc3.getString },
 | 
			
		||||
                'Model':                  { 'validator': sc3.getString },
 | 
			
		||||
                'Manufacturer':           { 'validator': sc3.getString },
 | 
			
		||||
                'Type':                   { 'validator': sc3.getString },
 | 
			
		||||
                'Unit':                   { 'validator': sc3.getString },
 | 
			
		||||
                'LowFrequency':           { 'validator': float },
 | 
			
		||||
                'HighFrequency':          { 'validator': float },
 | 
			
		||||
                'Response':               { 'validator': sc3.getString },
 | 
			
		||||
                'Remark':                 { 'validator': sc3.getBlob }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return(valid.get(mode))
 | 
			
		||||
 | 
			
		||||
    def __init__(self, mode, child=[]):
 | 
			
		||||
        self.sc3Mode = mode
 | 
			
		||||
        self.sc3obj = None
 | 
			
		||||
        self.sc3Valid = sc3._findValidOnes(mode)
 | 
			
		||||
        self._sc3Childs = child
 | 
			
		||||
 | 
			
		||||
    def _create(self):
 | 
			
		||||
        if not self.sc3Valid:
 | 
			
		||||
            raise Exception("Class without a type defined.")
 | 
			
		||||
        return self.sc3Valid['creator']()
 | 
			
		||||
            
 | 
			
		||||
    def sc3Att(self):
 | 
			
		||||
        """
 | 
			
		||||
        This is the heart. You should return an dictionary of attributes to be
 | 
			
		||||
        setted on the sc3 object. This dictionary will be used by the _fillSc3
 | 
			
		||||
        method.
 | 
			
		||||
        """
 | 
			
		||||
        raise Exception("Not Implemented !")
 | 
			
		||||
 | 
			
		||||
    def sc3ValidKey(self, key):
 | 
			
		||||
        if not self.sc3Valid:
 | 
			
		||||
            raise Exception("Class without a type defined.")
 | 
			
		||||
        return (key in self.sc3Valid['attributes'])
 | 
			
		||||
 | 
			
		||||
    def sc3Resolv(self, inventory):
 | 
			
		||||
        """
 | 
			
		||||
        In this method you should be able to resolv all the references in your
 | 
			
		||||
        self object.
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
        
 | 
			
		||||
    def sc3Derived(self, inventory):
 | 
			
		||||
        """
 | 
			
		||||
        This method should generate and collect all the derived objects
 | 
			
		||||
        (child on the inventory sense) that should be attributed to the self
 | 
			
		||||
        object. By default on this virtual method is returns an empty array.
 | 
			
		||||
        """
 | 
			
		||||
        objs = []
 | 
			
		||||
        for obj in self._sc3Childs:
 | 
			
		||||
            objs.append(obj.sc3Obj(inventory))
 | 
			
		||||
        return objs
 | 
			
		||||
 | 
			
		||||
    def sc3ID(self, inventory):
 | 
			
		||||
        obj = self.sc3Obj(inventory)
 | 
			
		||||
        return obj.publicID()
 | 
			
		||||
 | 
			
		||||
    def sc3Obj(self, inventory):
 | 
			
		||||
        if not self.sc3obj:
 | 
			
		||||
            # Get a new object
 | 
			
		||||
            obj = self._create()
 | 
			
		||||
 | 
			
		||||
            # try to resolve REFERENCES to PUBLIC ID
 | 
			
		||||
            self.sc3Resolv(inventory)
 | 
			
		||||
 | 
			
		||||
            # Add the derived objects in
 | 
			
		||||
            for dobj in self.sc3Derived(inventory):
 | 
			
		||||
                obj.add(dobj)
 | 
			
		||||
 | 
			
		||||
            # Fill the Attributes in
 | 
			
		||||
            self._fillSc3(obj, self.sc3Att())
 | 
			
		||||
            # # Only want to see Networks:
 | 
			
		||||
            # if (('Code' in self.sc3Att().keys())
 | 
			
		||||
            #     and ('ArchiveNetworkCode' not in self.sc3Att().keys())
 | 
			
		||||
            #     and ('Azimuth' not in self.sc3Att().keys())
 | 
			
		||||
            #     ):
 | 
			
		||||
            #     print('DEBUG basesc3.py: sc3Obj:', self, self.sc3Att())
 | 
			
		||||
 | 
			
		||||
            # Set as created
 | 
			
		||||
            self.sc3obj = obj
 | 
			
		||||
 | 
			
		||||
        # return the obj
 | 
			
		||||
        return self.sc3obj
 | 
			
		||||
							
								
								
									
										506
									
								
								lib/python/nettab/convertUtils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										506
									
								
								lib/python/nettab/convertUtils.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,506 @@
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
import sys
 | 
			
		||||
import csv
 | 
			
		||||
import re
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
def getFieldNames(fd):
 | 
			
		||||
    tmp = fd.readline().split(',')
 | 
			
		||||
    fieldNames = []
 | 
			
		||||
    for i in tmp:
 | 
			
		||||
        fieldNames.append(i.strip())
 | 
			
		||||
    return fieldNames
 | 
			
		||||
 | 
			
		||||
def quote(instr):
 | 
			
		||||
    return '"'+instr+'"'
 | 
			
		||||
 | 
			
		||||
def hummanStr(instr):
 | 
			
		||||
    return instr.replace("_"," ")
 | 
			
		||||
 | 
			
		||||
def parseDate(val):
 | 
			
		||||
    if not val or val == "":
 | 
			
		||||
        return None
 | 
			
		||||
    date=val.replace("/", "-")
 | 
			
		||||
    formats={ len("YYYY-JJJ") : "%Y-%j",
 | 
			
		||||
            len("YYYY-MM-DD") : "%Y-%m-%d",
 | 
			
		||||
            len("YYYY-JJJ:HHMM") : "%Y-%j:%H%M",
 | 
			
		||||
            len("YYYY-JJJTHH:MM") : "%Y-%jT%H:%M",
 | 
			
		||||
            len("YYYY-MM-DDTHH:MM") : "%Y-%m-%dT%H:%M",
 | 
			
		||||
            len("YYYY-JJJTHH:MM:SS") : "%Y-%jT%H:%M:%S",
 | 
			
		||||
            len("YYYY-MM-DDTHH:MM:SS") : "%Y-%m-%dT%H:%M:%S"}
 | 
			
		||||
    try:
 | 
			
		||||
        return datetime.strptime(date, formats[len(date)])
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        raise ValueError("invalid date: " + date + str(e))
 | 
			
		||||
 | 
			
		||||
def formatDate(date):
 | 
			
		||||
    if not date:
 | 
			
		||||
        return ""
 | 
			
		||||
 | 
			
		||||
    if date.hour != 0 or date.minute != 0:
 | 
			
		||||
        return datetime.strftime(date,"%Y/%j:%H%M")
 | 
			
		||||
    
 | 
			
		||||
    return datetime.strftime(date,"%Y/%j")
 | 
			
		||||
 | 
			
		||||
def isPyVersion(major, minor):
 | 
			
		||||
    return sys.version_info[0] == major and \
 | 
			
		||||
           sys.version_info[1] == minor
 | 
			
		||||
 | 
			
		||||
class StationMappings:
 | 
			
		||||
    def __init__(self, networkCode, stationList, filename):
 | 
			
		||||
        self.networkCode = networkCode
 | 
			
		||||
        self.stationList = stationList
 | 
			
		||||
        self.stationMapping = {}
 | 
			
		||||
        self.stationBreak = {}
 | 
			
		||||
 | 
			
		||||
        if not filename: return
 | 
			
		||||
        _rx_statmap = re.compile(r'\s*([^_]*)_([^=]*)=(\S*)\s*(from=([0-9]+/[0-9]+))?\s*(to=([0-9]+/[0-9]+))?\s*$')
 | 
			
		||||
        fd = open(filename)
 | 
			
		||||
        stationMapping = {}
 | 
			
		||||
        try:
 | 
			
		||||
            lineno = 0
 | 
			
		||||
            try:
 | 
			
		||||
                line = fd.readline()
 | 
			
		||||
                lineno = 1
 | 
			
		||||
                while line:
 | 
			
		||||
                    m = _rx_statmap.match(line)
 | 
			
		||||
                    if m is None:
 | 
			
		||||
                        raise Exception("parse error")
 | 
			
		||||
 | 
			
		||||
                    (sta, net, archive_net, from_def, from_year, to_def, to_year) = m.groups()
 | 
			
		||||
 | 
			
		||||
                    if net != self.networkCode:
 | 
			
		||||
                        line = fd.readline()
 | 
			
		||||
                        continue
 | 
			
		||||
 | 
			
		||||
                    if sta not in self.stationList:
 | 
			
		||||
                        line = fd.readline()
 | 
			
		||||
                        continue
 | 
			
		||||
                    
 | 
			
		||||
                    try:
 | 
			
		||||
                        sta_net = stationMapping[sta]
 | 
			
		||||
 | 
			
		||||
                    except KeyError:
 | 
			
		||||
                        sta_net = []
 | 
			
		||||
                        stationMapping[sta] = sta_net
 | 
			
		||||
 | 
			
		||||
                    if from_def:
 | 
			
		||||
                        from_date = parseDate(from_year)
 | 
			
		||||
 | 
			
		||||
                    else:
 | 
			
		||||
                        from_date = None
 | 
			
		||||
 | 
			
		||||
                    if to_def:
 | 
			
		||||
                        to_date = parseDate(to_year)
 | 
			
		||||
 | 
			
		||||
                    else:
 | 
			
		||||
                        to_date = None
 | 
			
		||||
 | 
			
		||||
                    sta_net.append((from_date, to_date, archive_net))
 | 
			
		||||
                    line = fd.readline()
 | 
			
		||||
                    lineno += 1
 | 
			
		||||
 | 
			
		||||
            except (Exception, TypeError, ValueError) as e:
 | 
			
		||||
                raise Exception("%s:%d: %s" % (file, lineno, str(e)))
 | 
			
		||||
 | 
			
		||||
        finally:
 | 
			
		||||
            fd.close()
 | 
			
		||||
            
 | 
			
		||||
        if len(stationMapping):
 | 
			
		||||
            print("Found %d station mappings" % len(stationMapping), file=sys.stderr)
 | 
			
		||||
            self.stationMapping = stationMapping
 | 
			
		||||
        else:
 | 
			
		||||
            ## print("No station mappings found", file=sys.stderr)
 | 
			
		||||
            pass
 | 
			
		||||
    
 | 
			
		||||
    def dump(self, fdo, stationCode):
 | 
			
		||||
        items = []
 | 
			
		||||
        for (code, mapping) in self.stationMapping.items():
 | 
			
		||||
            if stationCode and stationCode != code: continue
 | 
			
		||||
            items.append(code)
 | 
			
		||||
            for (fromDate, toDate, network) in mapping:
 | 
			
		||||
                fdo.write("Sa: ArchiveNetworkCode=%s %s" % (network, code))
 | 
			
		||||
                if fromDate:
 | 
			
		||||
                    fdo.write(" from=%s" % formatDate(fromDate))
 | 
			
		||||
                if toDate:
 | 
			
		||||
                    fdo.write(" to=%s" % formatDate(toDate))
 | 
			
		||||
                fdo.write("\n")
 | 
			
		||||
 | 
			
		||||
        for code in items:
 | 
			
		||||
            self.stationMapping.pop(code)
 | 
			
		||||
    
 | 
			
		||||
    def getMappings(self, code, start, end):
 | 
			
		||||
        mapping = []
 | 
			
		||||
        
 | 
			
		||||
        if (code, start, end) not in self.stationBreak:
 | 
			
		||||
            mapping.append([start, end])
 | 
			
		||||
        else:
 | 
			
		||||
            for (archiveNet, s, e, fr, to) in self.stationBreak[(code, start, end)]:
 | 
			
		||||
                mapping.append([s, e])
 | 
			
		||||
 | 
			
		||||
        return mapping
 | 
			
		||||
    
 | 
			
		||||
    def parseStationLine(self, items):
 | 
			
		||||
        stationCode = items[0].strip()
 | 
			
		||||
        start = parseDate(items[10])
 | 
			
		||||
        
 | 
			
		||||
        if len(items) > 11:
 | 
			
		||||
            end = parseDate(items[11])
 | 
			
		||||
        else:
 | 
			
		||||
            end = None
 | 
			
		||||
    
 | 
			
		||||
        if stationCode not in self.stationMapping:
 | 
			
		||||
            ## print("Skipping %s not in mapping list" % stationCode, file=sys.stderr)
 | 
			
		||||
            return self.getMappings(stationCode, start, end)
 | 
			
		||||
    
 | 
			
		||||
        for (fDate, tDate, archiveNet) in self.stationMapping[stationCode]:
 | 
			
		||||
            if fDate and tDate:
 | 
			
		||||
                raise Exception("Not Supported to and from definitions found.")
 | 
			
		||||
            elif fDate:
 | 
			
		||||
                if fDate >= start:
 | 
			
		||||
                    if (end and fDate <= end) or not end:
 | 
			
		||||
                        ## print("Processing fDate %s %s %s [%s]" % (stationCode, start, end, fDate), file=sys.stderr)
 | 
			
		||||
                        if (stationCode, start, end) in self.stationBreak:
 | 
			
		||||
                            raise Exception("Crazy multiple station mapping for the same station line")
 | 
			
		||||
                        self.stationBreak[(stationCode, start, end)] = []
 | 
			
		||||
                        self.stationBreak[(stationCode, start, end)].append((self.networkCode, start, fDate, fDate, tDate))
 | 
			
		||||
                        self.stationBreak[(stationCode, start, end)].append((archiveNet, fDate, end, fDate, tDate))
 | 
			
		||||
                        ## prin( " found mapping From -> %s (%s,%s)" % (fDate, stationCode, formatDate(start)), file=sys.stderr)
 | 
			
		||||
                        return self.getMappings(stationCode, start, end)
 | 
			
		||||
            elif tDate:
 | 
			
		||||
                if tDate >= start:
 | 
			
		||||
                    if (end and tDate <= end) or not end:
 | 
			
		||||
                        ## print("Processing tDate %s %s %s [%s]" % (stationCode, start, end, tDate), file=sys.stderr)
 | 
			
		||||
                        if (stationCode, start, end) in self.stationBreak:
 | 
			
		||||
                            raise Exception("Crazy multiple station mapping for the same station line")
 | 
			
		||||
                        self.stationBreak[(stationCode, start, end)] = []
 | 
			
		||||
                        self.stationBreak[(stationCode, start, end)].append((archiveNet, start, tDate, fDate, tDate))
 | 
			
		||||
                        self.stationBreak[(stationCode, start, end)].append((self.networkCode, tDate, end, fDate, tDate))
 | 
			
		||||
                        ## print(" found mapping To -> %s (%s,%s)" % (tDate, stationCode, formatDate(start)), file=sys.stderr)
 | 
			
		||||
                        return self.getMappings(stationCode, start, end)
 | 
			
		||||
            else:
 | 
			
		||||
                if (stationCode, start, end) in self.stationBreak:
 | 
			
		||||
                    raise Exception("Crazy multiple station mapping for the same station line")
 | 
			
		||||
                self.stationBreak[(stationCode, start, end)] = []
 | 
			
		||||
                self.stationBreak[(stationCode, start, end)].append((archiveNet, start, end, fDate, tDate))
 | 
			
		||||
                ## print(" found mapping ALL (%s,%s)" % (stationCode, formatDate(start)), file=sys.stderr)
 | 
			
		||||
                return self.getMappings(stationCode, start, end)
 | 
			
		||||
        ## print("Ignored %s" % " ".join(items), file=sys.stderr)
 | 
			
		||||
        return self.getMappings(stationCode, start, end)
 | 
			
		||||
    
 | 
			
		||||
class StationAttributes:
 | 
			
		||||
    def __init__(self, networkCode, stationList, filename):
 | 
			
		||||
        self.networkCode= networkCode
 | 
			
		||||
        self.stationList = stationList
 | 
			
		||||
        self.stationAttributeList = {}
 | 
			
		||||
        
 | 
			
		||||
        if not filename: return
 | 
			
		||||
        
 | 
			
		||||
        fd = open(filename)
 | 
			
		||||
        attributes = {}
 | 
			
		||||
        try:
 | 
			
		||||
            try:
 | 
			
		||||
                fieldNames = None
 | 
			
		||||
                if isPyVersion(2, 3):
 | 
			
		||||
                    fieldNames = getFieldNames(fd)
 | 
			
		||||
    
 | 
			
		||||
                for row in csv.DictReader(fd, fieldNames):
 | 
			
		||||
                    net_code = row['net_code']
 | 
			
		||||
                    if net_code != self.networkCode: continue
 | 
			
		||||
                    
 | 
			
		||||
                    sta_code = row['sta_code']
 | 
			
		||||
                    if sta_code not in self.stationList: continue
 | 
			
		||||
                    
 | 
			
		||||
                    start = parseDate(row['start'].strip())
 | 
			
		||||
                                        
 | 
			
		||||
                    if sta_code in attributes:
 | 
			
		||||
                        raise Exception("multiple %s found in %s" % (str((net_code, sta_code, row['start'])), filename))
 | 
			
		||||
                    
 | 
			
		||||
                    del row['net_code']
 | 
			
		||||
                    del row['sta_code']
 | 
			
		||||
                    del row['start']
 | 
			
		||||
                    
 | 
			
		||||
                    ## Clean up input
 | 
			
		||||
                    for key in ['restricted', 'restricted_exc', 'place', 'country', 'affiliation', 'remark']:
 | 
			
		||||
                        row[key] = row[key].strip()
 | 
			
		||||
                        if len(row[key]) == 0:
 | 
			
		||||
                            del row[key]
 | 
			
		||||
                    
 | 
			
		||||
                    if 'restricted' in row:
 | 
			
		||||
                        row['restricted'] = bool(int(row['restricted']))
 | 
			
		||||
                        if not row['restricted']: del (row['restricted'])
 | 
			
		||||
                    
 | 
			
		||||
                    if row:
 | 
			
		||||
                        attributes[sta_code] = row
 | 
			
		||||
 | 
			
		||||
            except KeyError as e:
 | 
			
		||||
                raise Exception("column %s missing in %s" % (str(e), filename))
 | 
			
		||||
    
 | 
			
		||||
            except (TypeError, ValueError) as e:
 | 
			
		||||
                raise Exception("error reading %s: %s" % (filename, str(e)))
 | 
			
		||||
            
 | 
			
		||||
        finally:
 | 
			
		||||
            fd.close()
 | 
			
		||||
        self.stationAttributeList = self.__build__(attributes)
 | 
			
		||||
        print(" loaded attributes for %d stations on network %s (%s)" % (len(self.stationAttributeList), self.networkCode, filename), file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
    def __build__(self, attributes):
 | 
			
		||||
        newat = {}
 | 
			
		||||
 | 
			
		||||
        if not attributes:
 | 
			
		||||
            ## print("no station attributes found for network %s" % self.networkCode, file=sys.stderr)
 | 
			
		||||
            return newat
 | 
			
		||||
        
 | 
			
		||||
        for (code,row) in attributes.items():
 | 
			
		||||
            nr = {}
 | 
			
		||||
            for (k,v) in row.items():
 | 
			
		||||
                if k == 'country': k = 'Country'
 | 
			
		||||
                if k == 'place': k = 'Place'
 | 
			
		||||
                if k == 'affiliation': k = 'Affiliation'
 | 
			
		||||
                if k == 'remark': k = 'Remark'
 | 
			
		||||
                if k == 'restricted': k = 'Restricted'
 | 
			
		||||
                nr[k] = v
 | 
			
		||||
            if nr:
 | 
			
		||||
                newat[code] = nr
 | 
			
		||||
        return newat
 | 
			
		||||
 | 
			
		||||
    def get(self, code):
 | 
			
		||||
        if self.stationAttributeList and code in self.stationAttributeList:
 | 
			
		||||
            return self.stationAttributeList[code]
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
    
 | 
			
		||||
    def __parseDescription__(self, description):
 | 
			
		||||
        affiliation = None
 | 
			
		||||
        place = None
 | 
			
		||||
        country = None
 | 
			
		||||
        description = hummanStr(description)
 | 
			
		||||
        hasStation = True if description.find("Station") >= 0 else False
 | 
			
		||||
        
 | 
			
		||||
        if hasStation:
 | 
			
		||||
            affiliation = description[0:(description.index("Station"))].strip()
 | 
			
		||||
            parts = description[description.index("Station")+7:].strip().split(",")
 | 
			
		||||
        else:
 | 
			
		||||
            parts = description.split(",")
 | 
			
		||||
        
 | 
			
		||||
        if len(parts) > 1:
 | 
			
		||||
            country = parts[len(parts)-1].strip()
 | 
			
		||||
            parts = parts[0:(len(parts)-1)]
 | 
			
		||||
            place = ",".join(parts)
 | 
			
		||||
        else:
 | 
			
		||||
            place = ",".join(parts)
 | 
			
		||||
        
 | 
			
		||||
#        print("Country:", country, file=sys.stderr)
 | 
			
		||||
#        print("Place:", place, file=sys.stderr)
 | 
			
		||||
#        print("Affiliation:", affiliation, file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
        oui = {}
 | 
			
		||||
        if country:
 | 
			
		||||
            oui['Country'] = country
 | 
			
		||||
        if place:
 | 
			
		||||
            oui['Place'] = place
 | 
			
		||||
        if affiliation:
 | 
			
		||||
            oui['Affiliation'] = affiliation
 | 
			
		||||
        return oui
 | 
			
		||||
 | 
			
		||||
    def reorder_station_attr(self):
 | 
			
		||||
        att = {}
 | 
			
		||||
        if not self.stationAttributeList:
 | 
			
		||||
            return None
 | 
			
		||||
        
 | 
			
		||||
        for (code, row) in self.stationAttributeList.items():
 | 
			
		||||
            for (k, v) in row.items():
 | 
			
		||||
                if k == 'restricted_exc':
 | 
			
		||||
                    k = 'Restricted'
 | 
			
		||||
                    extra=',*,'+str(v)
 | 
			
		||||
                    v = (not row['Restricted']) if 'Restricted' in row else True 
 | 
			
		||||
                else:
 | 
			
		||||
                    extra= ''
 | 
			
		||||
                
 | 
			
		||||
                try:
 | 
			
		||||
                    dk = att[k]
 | 
			
		||||
                except:
 | 
			
		||||
                    dk = {}
 | 
			
		||||
                    att[k] = dk
 | 
			
		||||
 | 
			
		||||
                try:
 | 
			
		||||
                    dv = dk[str(v)]
 | 
			
		||||
                except:
 | 
			
		||||
                    dv = []
 | 
			
		||||
                    dk[str(v)] = dv
 | 
			
		||||
                
 | 
			
		||||
                dv.append(code+extra)
 | 
			
		||||
        return att
 | 
			
		||||
 | 
			
		||||
    def parseStationLine(self, items, fStart = None, fEnd = None):
 | 
			
		||||
        stationCode = items[0].strip()
 | 
			
		||||
        description = items[1]
 | 
			
		||||
        start =  parseDate(items[10])
 | 
			
		||||
        if stationCode not in self.stationList:
 | 
			
		||||
            raise Exception("Station %s not in station list." % stationCode)
 | 
			
		||||
        
 | 
			
		||||
        ## Here we can force a different start & End values to the line
 | 
			
		||||
        if fStart is not None:
 | 
			
		||||
            start = fStart
 | 
			
		||||
            
 | 
			
		||||
        if fEnd is not None:
 | 
			
		||||
            end = fEnd
 | 
			
		||||
        
 | 
			
		||||
        oui = None
 | 
			
		||||
        at = self.get(stationCode)
 | 
			
		||||
        #print >>sys.stderr,items, at, file=sys.stderr)
 | 
			
		||||
        if not at:
 | 
			
		||||
            ## print(" Deriving attributes from description %s " % " ".join(items), file=sys.stderr)
 | 
			
		||||
            at = self.__parseDescription__(description)
 | 
			
		||||
            if at:
 | 
			
		||||
                self.stationAttributeList[stationCode] = at
 | 
			
		||||
        else:
 | 
			
		||||
            for item in ['Affiliation', 'Country', 'Place']:
 | 
			
		||||
                if item in at:
 | 
			
		||||
                    continue
 | 
			
		||||
                if not oui:
 | 
			
		||||
                    ## print(" Deriving attribute (%s) from description %s " % (item, " ".join(items)), file=sys.stderr)
 | 
			
		||||
                    oui = self.__parseDescription__(description)
 | 
			
		||||
                if item in oui:
 | 
			
		||||
                    ## print(" Setting attribute (%s) from description for %s = %s" % (item, stationCode, oui[item]), file=sys.stderr)
 | 
			
		||||
                    at[item] = oui[item]
 | 
			
		||||
                else:
 | 
			
		||||
                    ## print(" Empty %s for %s" % (item, stationCode), file=sys.stderr)
 | 
			
		||||
                    pass
 | 
			
		||||
        
 | 
			
		||||
        country = at['Country'] if 'Country' in at else None
 | 
			
		||||
        place =  at['Place'] if 'Place' in at else None
 | 
			
		||||
        return [place, country]
 | 
			
		||||
 | 
			
		||||
    def dump(self, fdo, code):
 | 
			
		||||
        if not code:
 | 
			
		||||
            att = self.reorder_station_attr()
 | 
			
		||||
            for (key,v) in att.items():
 | 
			
		||||
                if key in ['Country', 'Place']: continue
 | 
			
		||||
                for (value, s) in v.items():
 | 
			
		||||
                    fdo.write("Sa: %s=%s" % (key, quote(value)))
 | 
			
		||||
                    for station in s:
 | 
			
		||||
                            fdo.write(" %s" % (station))
 | 
			
		||||
                    fdo.write("\n")
 | 
			
		||||
        else:
 | 
			
		||||
            at = self.get(code)
 | 
			
		||||
            if not at: return
 | 
			
		||||
            if 'done' in at: return
 | 
			
		||||
            at['done'] = 1 # Mark the item as printed
 | 
			
		||||
            for (k,v) in at.items():
 | 
			
		||||
                extra = ''
 | 
			
		||||
                if k in [ 'done', 'Place', 'Country']: continue
 | 
			
		||||
                if k in ['Affiliation']: v = quote(v)
 | 
			
		||||
 | 
			
		||||
                if k == 'Restricted':
 | 
			
		||||
                    extra = ' %s,*,*' % code
 | 
			
		||||
 | 
			
		||||
                if k == 'restricted_exc':
 | 
			
		||||
                    k = 'Restricted'
 | 
			
		||||
                    extra=',*,'+str(v)
 | 
			
		||||
                    v = (not at['Restricted']) if 'Restricted' in at else True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                fdo.write("Sa: %s=%s %s%s\n" % (k,v,code,extra))
 | 
			
		||||
 | 
			
		||||
class NetworkAttributes:
 | 
			
		||||
    def __build__(self, row):
 | 
			
		||||
        #net_code,start,end,restricted,shared,net_class,type,institutions,region,remark
 | 
			
		||||
        
 | 
			
		||||
        attList = {}
 | 
			
		||||
        
 | 
			
		||||
        if row['start']:
 | 
			
		||||
            self.start = row['start'].strftime("%Y/%j")
 | 
			
		||||
            self.startDate = row['start']
 | 
			
		||||
            self.hasStart = True
 | 
			
		||||
        
 | 
			
		||||
        if row['end']:
 | 
			
		||||
            self.end = row['end'].strftime("%Y/%j")
 | 
			
		||||
            self.endDate = row['end']
 | 
			
		||||
            self.hasEnd = True
 | 
			
		||||
        
 | 
			
		||||
        if row['restricted'] != 0:
 | 
			
		||||
            attList['Restricted'] = row['restricted']
 | 
			
		||||
        
 | 
			
		||||
        if row['shared'] != 1:
 | 
			
		||||
            attList['Shared'] = row['shared']
 | 
			
		||||
        
 | 
			
		||||
        if row['net_class']:
 | 
			
		||||
            attList['NetClass'] = row['net_class'].strip()
 | 
			
		||||
        
 | 
			
		||||
        if row['type']:
 | 
			
		||||
            attList['Type'] = row['type'].strip()
 | 
			
		||||
        
 | 
			
		||||
        if row['institutions']:
 | 
			
		||||
            attList['Institutions'] = row['institutions'].strip()
 | 
			
		||||
        
 | 
			
		||||
        if row['region']:
 | 
			
		||||
            attList['Region'] = row['region'].strip()
 | 
			
		||||
        
 | 
			
		||||
        if row['remark']:
 | 
			
		||||
            attList['Remark'] = row['remark'].strip()
 | 
			
		||||
        
 | 
			
		||||
        self.networkAttributes.update(attList)
 | 
			
		||||
 | 
			
		||||
    def parseNetworkLine(self, items):
 | 
			
		||||
        if len(items) < 4 or len(items) > 6:
 | 
			
		||||
            raise Exception("Invalid network line")
 | 
			
		||||
        
 | 
			
		||||
        attList = {}
 | 
			
		||||
        if items[1] == "none":
 | 
			
		||||
            attList['Description'] = hummanStr(items[0])
 | 
			
		||||
        else:
 | 
			
		||||
            attList['Description'] = "%s (%s)" % (hummanStr(items[0]), items[1])
 | 
			
		||||
        
 | 
			
		||||
        self.networkAttributes.update(attList)
 | 
			
		||||
 | 
			
		||||
    def dump(self, fdo):
 | 
			
		||||
        for (k,v) in self.networkAttributes.items():
 | 
			
		||||
            if k in ['Description', 'Remark', 'Region', 'Institutions']:
 | 
			
		||||
                v = quote(v)
 | 
			
		||||
            fdo.write("Na: %s=%s\n" % (k,v))
 | 
			
		||||
 | 
			
		||||
    def __init__(self, networkCode, filename):
 | 
			
		||||
        self.networkCode = networkCode
 | 
			
		||||
        self.networkAttributes = {}
 | 
			
		||||
 | 
			
		||||
        self.start = None
 | 
			
		||||
        self.end = None
 | 
			
		||||
        
 | 
			
		||||
        self.hasStart = False
 | 
			
		||||
        self.hasEnd = False
 | 
			
		||||
         
 | 
			
		||||
        if not filename: return
 | 
			
		||||
        fd = open(filename)
 | 
			
		||||
        try:
 | 
			
		||||
            try:
 | 
			
		||||
                fieldNames = None
 | 
			
		||||
                if isPyVersion(2, 3):
 | 
			
		||||
                    fieldNames = getFieldNames(fd)
 | 
			
		||||
 | 
			
		||||
                for row in csv.DictReader(fd, fieldNames):
 | 
			
		||||
                    net_code = row['net_code']
 | 
			
		||||
                    if net_code != self.networkCode: continue 
 | 
			
		||||
 | 
			
		||||
                    #del row['net_code']
 | 
			
		||||
                    #del row['start']
 | 
			
		||||
                    row['start'] = parseDate(row['start'])
 | 
			
		||||
                    row['end'] = parseDate(row['end'])
 | 
			
		||||
                    row['restricted'] = bool(int(row['restricted']))
 | 
			
		||||
                    row['shared'] = bool(int(row['shared']))
 | 
			
		||||
                    row['region'] = row['region'].strip()
 | 
			
		||||
                    row['remark'] = row['remark'].strip()
 | 
			
		||||
                    row['institutions'] = row['institutions'].strip()
 | 
			
		||||
                                                                                
 | 
			
		||||
                    self.__build__(row)
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
            except KeyError as e:
 | 
			
		||||
                raise Exception("column %s missing in %s" % (str(e), filename))
 | 
			
		||||
 | 
			
		||||
            except (TypeError, ValueError) as e:
 | 
			
		||||
                raise Exception("error reading %s: %s" % (filename, str(e)))
 | 
			
		||||
 | 
			
		||||
        finally:
 | 
			
		||||
            fd.close()
 | 
			
		||||
        print(" found %d Attribute for network %s (%s)" % (len(self.networkAttributes), self.networkCode, filename), file=sys.stderr)
 | 
			
		||||
							
								
								
									
										160
									
								
								lib/python/nettab/helpers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								lib/python/nettab/helpers.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,160 @@
 | 
			
		||||
import re
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
import string
 | 
			
		||||
from functools import reduce
 | 
			
		||||
 | 
			
		||||
class parsers(object):
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def parseString(val):
 | 
			
		||||
        return val.strip()
 | 
			
		||||
    
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _parse_paz(npaz, s):
 | 
			
		||||
        _rx_paz = re.compile(r'\s*([0-9]*)\(\s*([^,]+),\s*([^)]+)\)\s*')
 | 
			
		||||
        pos = 0
 | 
			
		||||
        n = 0
 | 
			
		||||
        c = []
 | 
			
		||||
        while pos < len(s):
 | 
			
		||||
            m = _rx_paz.match(s, pos)
 | 
			
		||||
            if m is None:
 | 
			
		||||
                raise Exception("error parsing PAZ at '" + s[pos:] + "'")
 | 
			
		||||
    
 | 
			
		||||
            try:
 | 
			
		||||
                if len(m.group(1)) > 0:
 | 
			
		||||
                    x = int(m.group(1))
 | 
			
		||||
                else:
 | 
			
		||||
                    x = 1
 | 
			
		||||
                    
 | 
			
		||||
                rv = m.group(2)
 | 
			
		||||
                iv = m.group(3)
 | 
			
		||||
    
 | 
			
		||||
                float(rv)
 | 
			
		||||
                float(iv)
 | 
			
		||||
    
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                raise Exception("error parsing PAZ at '" + s[pos:] + "'")
 | 
			
		||||
    
 | 
			
		||||
            for i in range(0, x):
 | 
			
		||||
                c.append((rv, iv))
 | 
			
		||||
                i = i
 | 
			
		||||
            
 | 
			
		||||
            n += x
 | 
			
		||||
            pos = m.end()
 | 
			
		||||
        
 | 
			
		||||
        if n != npaz:
 | 
			
		||||
            raise Exception("expected %d PAZ, found %d" % (npaz, n))
 | 
			
		||||
        return c
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _normalize(num, denom):
 | 
			
		||||
        if num > denom:
 | 
			
		||||
            (a, b) = (num, denom)
 | 
			
		||||
        else:
 | 
			
		||||
            (a, b) = (denom, num)
 | 
			
		||||
    
 | 
			
		||||
        while b > 1:
 | 
			
		||||
            (a, b) = (b, a % b)
 | 
			
		||||
    
 | 
			
		||||
        if b == 0:
 | 
			
		||||
            return (num / a, denom / a)
 | 
			
		||||
    
 | 
			
		||||
        return (num, denom)
 | 
			
		||||
    
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _rational(x):
 | 
			
		||||
        sign, mantissa, exponent = x.as_tuple()
 | 
			
		||||
        sign = (1, -1)[sign]
 | 
			
		||||
        mantissa = sign * reduce(lambda a, b: 10 * a + b, mantissa)
 | 
			
		||||
        if exponent < 0:
 | 
			
		||||
            return parsers._normalize(mantissa, 10 ** (-exponent))
 | 
			
		||||
        else:
 | 
			
		||||
            return (mantissa * 10 ** exponent, 1)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _parseFloat(val, mi=None , ma= None):
 | 
			
		||||
        number = float(val)
 | 
			
		||||
        if (mi and number < mi) or (ma and number > ma):
 | 
			
		||||
            raise Exception("Invalid Range")
 | 
			
		||||
        return number
 | 
			
		||||
    
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def parseGain(val):
 | 
			
		||||
        try:
 | 
			
		||||
            return parsers._parseFloat(val, 0.0, None)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            raise Exception("Invalid Gain: %s" % e)
 | 
			
		||||
    
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def parseLongitude(val):
 | 
			
		||||
        try:
 | 
			
		||||
            return parsers._parseFloat(val, -180.0, 180.0)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            raise Exception("Invalid Longitude: %s" % e)
 | 
			
		||||
    
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def parseLatitude(val):
 | 
			
		||||
        try:
 | 
			
		||||
            return parsers._parseFloat(val, -90.0, 90.0)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            raise Exception("Invalid Latitude: %s" % e)
 | 
			
		||||
    
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def parseDepth(val):
 | 
			
		||||
        # Deepest mine ~ 5000 m
 | 
			
		||||
        try:
 | 
			
		||||
            return parsers._parseFloat(val, 0.0, 5000)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            raise Exception("Invalid Depth: %s" % e)
 | 
			
		||||
    
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def parseElevation(val):
 | 
			
		||||
        # Highest Everest ~8500 m
 | 
			
		||||
        # Deepest Mariana ~11000 m
 | 
			
		||||
        try:
 | 
			
		||||
            return parsers._parseFloat(val, -11000, 9000)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            raise Exception("Invalid Elevation: %s" % e)
 | 
			
		||||
 | 
			
		||||
    @staticmethod    
 | 
			
		||||
    def parseDate(val):
 | 
			
		||||
        date=val.replace("/", "-")
 | 
			
		||||
        formats={ len("YYYY-JJJ") : "%Y-%j",
 | 
			
		||||
                len("YYYY-MM-DD") : "%Y-%m-%d",
 | 
			
		||||
                len("YYYY-JJJ:HHMM") : "%Y-%j:%H%M",
 | 
			
		||||
                len("YYYY-JJJTHH:MM") : "%Y-%jT%H:%M",
 | 
			
		||||
                len("YYYY-MM-DDTHH:MM") : "%Y-%m-%dT%H:%M",
 | 
			
		||||
                len("YYYY-JJJTHH:MM:SS") : "%Y-%jT%H:%M:%S",
 | 
			
		||||
                len("YYYY-MM-DDTHH:MM:SS") : "%Y-%m-%dT%H:%M:%S"}
 | 
			
		||||
        try:
 | 
			
		||||
            return datetime.strptime(date, formats[len(date)])
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            raise ValueError("invalid date: " + date + str(e))
 | 
			
		||||
 | 
			
		||||
    @staticmethod    
 | 
			
		||||
    def parseLocationCode(val):
 | 
			
		||||
        Code = val.strip()
 | 
			
		||||
        if len(Code) > 2 or len(re.sub("[A-Z0-9-*?]","",Code)) > 0:
 | 
			
		||||
            raise Exception("wrong code for location: %s" % Code)
 | 
			
		||||
        return Code
 | 
			
		||||
 | 
			
		||||
    @staticmethod    
 | 
			
		||||
    def parseStationCode(val):
 | 
			
		||||
        Code = val.strip()
 | 
			
		||||
        if not Code or len(Code) > 5 or  len(re.sub("[A-Z0-9*?]","",Code)) > 0:
 | 
			
		||||
            raise Exception("Wrong code for station: %s" % Code)
 | 
			
		||||
        return Code
 | 
			
		||||
 | 
			
		||||
    @staticmethod    
 | 
			
		||||
    def parseChannelCode(val):
 | 
			
		||||
        Code = val.strip()
 | 
			
		||||
        if not Code or len(Code) > 3 or len(re.sub("[A-Z0-9*?]","",Code)) > 0:
 | 
			
		||||
            raise Exception("Wrong code for channel: %s" % Code)
 | 
			
		||||
        return Code
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def parseNetworkCode(val):
 | 
			
		||||
        Code = val.strip()
 | 
			
		||||
        if not Code or len(Code) > 2 or len(re.sub("[A-Z0-9*?]","",Code)) > 0:
 | 
			
		||||
            raise Exception("Wrong code for network: %s" % Code)
 | 
			
		||||
        return Code
 | 
			
		||||
							
								
								
									
										1119
									
								
								lib/python/nettab/lineType.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1119
									
								
								lib/python/nettab/lineType.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1645
									
								
								lib/python/nettab/nettab.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1645
									
								
								lib/python/nettab/nettab.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										523
									
								
								lib/python/nettab/nodesi.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										523
									
								
								lib/python/nettab/nodesi.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,523 @@
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
from .lineType import Dl, Se, Ff, Pz, Cl
 | 
			
		||||
from .basesc3 import sc3
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
class prefixable(object):
 | 
			
		||||
	def adjust(self, prefix):
 | 
			
		||||
		if prefix:
 | 
			
		||||
			self.id = "%s:%s" % (prefix, self.id)
 | 
			
		||||
 | 
			
		||||
class Instruments(object):
 | 
			
		||||
	def __init__(self, prefix=""):
 | 
			
		||||
		self.keys = []
 | 
			
		||||
		self.ses = {}
 | 
			
		||||
		self.dls = {}
 | 
			
		||||
		self.fls = {}
 | 
			
		||||
		self.cls = {}
 | 
			
		||||
		self._sensors = {}
 | 
			
		||||
		self._datalogger = {}
 | 
			
		||||
		self._filters = {}
 | 
			
		||||
		self._Cal = {}
 | 
			
		||||
		self._prefix = prefix
 | 
			
		||||
 | 
			
		||||
	def sc3Objs(self):
 | 
			
		||||
		objs = []
 | 
			
		||||
		
 | 
			
		||||
		for s in list(self._sensors.values()):
 | 
			
		||||
			objs.append(s.sc3Obj(self))
 | 
			
		||||
 | 
			
		||||
		for s in list(self._datalogger.values()):
 | 
			
		||||
			objs.append(s.sc3Obj(self))
 | 
			
		||||
 | 
			
		||||
		for s in list(self._filters.values()):
 | 
			
		||||
			objs.append(s.sc3Obj(self))
 | 
			
		||||
 | 
			
		||||
		return objs
 | 
			
		||||
 | 
			
		||||
	def add(self, obj):
 | 
			
		||||
		where = None
 | 
			
		||||
 | 
			
		||||
		if isinstance(obj, Se):
 | 
			
		||||
			where = self.ses
 | 
			
		||||
		elif isinstance(obj, Dl):
 | 
			
		||||
			where = self.dls
 | 
			
		||||
		elif isinstance(obj, Cl):
 | 
			
		||||
			where = self.cls 
 | 
			
		||||
		elif isinstance(obj, Ff) or isinstance(obj, Pz):
 | 
			
		||||
			where = self.fls
 | 
			
		||||
		else:
 | 
			
		||||
			raise Exception("Object type %s doesn't fir this class" % type(obj))
 | 
			
		||||
		
 | 
			
		||||
		if obj.id in self.keys:
 | 
			
		||||
			raise Exception("Object id %s already exist." % (obj)) 
 | 
			
		||||
 | 
			
		||||
		self.keys.append(obj.id)
 | 
			
		||||
		where[obj.id] = obj
 | 
			
		||||
	
 | 
			
		||||
		return
 | 
			
		||||
	
 | 
			
		||||
	def instrumentId(self, iid, gain):
 | 
			
		||||
		if gain is None:
 | 
			
		||||
			if iid in self.dls:
 | 
			
		||||
				gain = self.dls[iid].gain
 | 
			
		||||
			elif iid in self.ses:
 | 
			
		||||
				gain = self.ses[iid].gain
 | 
			
		||||
			else:
 | 
			
		||||
				raise Exception("Instrument iid not found")
 | 
			
		||||
 | 
			
		||||
		siid = "%s/g=%s" % (iid, int(float(gain)))
 | 
			
		||||
		return siid
 | 
			
		||||
	
 | 
			
		||||
	def loadDataloggerCalibrations(self, dsm, dsn, dch, dsg, start, end, dd):
 | 
			
		||||
		cls = []
 | 
			
		||||
		for cl in self.cls.values():
 | 
			
		||||
			if cl.type != "L": continue
 | 
			
		||||
			if cl.match(dsm, dsn):
 | 
			
		||||
				cls.append(Calibration(cl, dch, start, end))
 | 
			
		||||
 | 
			
		||||
		if len(cls) == 0:
 | 
			
		||||
			if dsn in self.cls:
 | 
			
		||||
				print("[%s] No calibrations found for serial number %s and model %s " % (dsm, dsn, dsm), file=sys.stderr)
 | 
			
		||||
			return
 | 
			
		||||
 | 
			
		||||
		diid = self.instrumentId(dsm, dsg)
 | 
			
		||||
		try:
 | 
			
		||||
			datalogger = self._datalogger[diid].sc3Obj(self)
 | 
			
		||||
			if dd != datalogger.publicID():
 | 
			
		||||
				raise Exception("Public Id doesn't match")
 | 
			
		||||
		except:
 | 
			
		||||
			raise Exception("[%s] Could not retrieve datalogger %s" % (dsm, diid))
 | 
			
		||||
	
 | 
			
		||||
		for cl in cls:
 | 
			
		||||
			if (dsm, dsn, dch, start, end) in self._Cal: 
 | 
			
		||||
				## print >> sys.stderr,"[%s] Skiping calibration channel %s" %  (dsm, cl.channel)
 | 
			
		||||
				continue
 | 
			
		||||
			## print >> sys.stderr,"[%s] Adding calibration %s (%s)" % (dsm, cl.channel, dd)
 | 
			
		||||
			datalogger.add(cl.sc3Obj(self))
 | 
			
		||||
			self._Cal[(dsm, dsn, dch, start, end)] = cl
 | 
			
		||||
 | 
			
		||||
	def loadSensorCalibrations(self, ssm, ssn, sch, ssg, start, end, ss):
 | 
			
		||||
		cls = []
 | 
			
		||||
		for cl in self.cls.values():
 | 
			
		||||
			if cl.type != "S": continue
 | 
			
		||||
			if cl.match(ssm, ssn):
 | 
			
		||||
				cls.append(Calibration(cl, sch, start, end))
 | 
			
		||||
 | 
			
		||||
		if len(cls) == 0:
 | 
			
		||||
			if ssn in self.cls:
 | 
			
		||||
				print("[%s] No calibrations found for serial number %s and model %s " % (ssm,ssn, ssm), file=sys.stderr)
 | 
			
		||||
			return
 | 
			
		||||
		
 | 
			
		||||
		siid = self.instrumentId(ssm, ssg)	
 | 
			
		||||
		try:
 | 
			
		||||
			sensor = self._sensors[siid].sc3Obj(self)
 | 
			
		||||
			if ss != sensor.publicID():
 | 
			
		||||
				raise Exception("Public Id doesn't match")
 | 
			
		||||
		except:
 | 
			
		||||
			raise Exception("[%s] Could not retrieve sensor %s" % (ssm, siid))
 | 
			
		||||
 | 
			
		||||
		for cl in cls:
 | 
			
		||||
			if (ssm, ssn, sch, start, end) in self._Cal: 
 | 
			
		||||
				## print >> sys.stderr,"[%s] Skiping calibration channel %s" %  (ssm, cl.channel)
 | 
			
		||||
				continue
 | 
			
		||||
			## print >> sys.stderr,"[%s] Adding calibration %s channel %s start %s" % (ssm, ssn, cl.channel, start)
 | 
			
		||||
			sensor.add(cl.sc3Obj(self))
 | 
			
		||||
			self._Cal[(ssm, ssn, sch, start, end)] = cl
 | 
			
		||||
	
 | 
			
		||||
	def check(self, networks):
 | 
			
		||||
		error = []
 | 
			
		||||
		
 | 
			
		||||
		# Dataloggers check
 | 
			
		||||
		error.append("* Dataloggers:")
 | 
			
		||||
		for dl in self.dls.values():
 | 
			
		||||
			error.extend(dl.check(self))
 | 
			
		||||
		error.append("")
 | 
			
		||||
		
 | 
			
		||||
		# Check fir filters
 | 
			
		||||
		error.append("* Filters:")
 | 
			
		||||
		for f in self.fls.values():
 | 
			
		||||
			c = False
 | 
			
		||||
			for dl in self.dls.values():
 | 
			
		||||
				c = c or dl.use(f)
 | 
			
		||||
				if c: break
 | 
			
		||||
			if not c: error.append(" [%s] filter is not used" % f.id)
 | 
			
		||||
		error.append("")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		# Check the calibrations
 | 
			
		||||
		error.append("* Calibrations:")
 | 
			
		||||
		for cl in self.cls.values():
 | 
			
		||||
			error.extend(cl.check(self))
 | 
			
		||||
		error.append("")
 | 
			
		||||
		
 | 
			
		||||
		
 | 
			
		||||
		error.append("* Sensors:")
 | 
			
		||||
		for f in self.ses.values():
 | 
			
		||||
			c = False
 | 
			
		||||
			for network in networks.values():
 | 
			
		||||
				for station in network.stations:
 | 
			
		||||
					for location in station.locations:
 | 
			
		||||
						for channel in location.channels:
 | 
			
		||||
							c = c or channel.use(f)
 | 
			
		||||
							if c: break
 | 
			
		||||
						if c: break
 | 
			
		||||
					if c: break
 | 
			
		||||
				if c: break
 | 
			
		||||
			if not c: error.append(" [%s] sensor is not used" % f.id)
 | 
			
		||||
		error.append("")
 | 
			
		||||
 | 
			
		||||
		error.append("* Dataloggers:")
 | 
			
		||||
		for f in self.dls.values():
 | 
			
		||||
			c = False
 | 
			
		||||
			for network in networks.values():
 | 
			
		||||
				c = c or network.use(f)
 | 
			
		||||
				if c: break
 | 
			
		||||
			if not c: error.append(" [%s] datalogger is not used" % f.id)
 | 
			
		||||
		error.append("")
 | 
			
		||||
		
 | 
			
		||||
		return error
 | 
			
		||||
 | 
			
		||||
	def filterType(self, iid):
 | 
			
		||||
		if iid not in self.keys:
 | 
			
		||||
			raise Exception("[%s] Filter id not found" % iid)
 | 
			
		||||
		
 | 
			
		||||
		if iid not in self.fls:
 | 
			
		||||
			raise Exception("[%s] Object is not a filter" % iid)
 | 
			
		||||
	
 | 
			
		||||
		obj = self.fls[iid]
 | 
			
		||||
		if isinstance(obj, Ff):
 | 
			
		||||
			fType = 'D'
 | 
			
		||||
		elif isinstance(obj, Pz):
 | 
			
		||||
			fType = obj.type
 | 
			
		||||
		
 | 
			
		||||
		return fType
 | 
			
		||||
	
 | 
			
		||||
	def filterID(self, iid):
 | 
			
		||||
		if iid not in self.keys:
 | 
			
		||||
			raise Exception("[%s] Filter id not found" % iid)
 | 
			
		||||
		
 | 
			
		||||
		if iid not in self.fls:
 | 
			
		||||
			raise Exception("[%s] Object is not a filter" % iid)
 | 
			
		||||
	
 | 
			
		||||
		if iid not in self._filters:
 | 
			
		||||
			obj = self.fls[iid]
 | 
			
		||||
			if isinstance(obj, Pz):
 | 
			
		||||
				## print >> sys.stderr," Generating new Filter (PZ): %s %s" % (iid,obj.type)
 | 
			
		||||
				newFilter = Paz(obj)
 | 
			
		||||
			elif isinstance(obj, Ff):
 | 
			
		||||
				## print >> sys.stderr," Generating new Filter (Fir): %s" % (iid)
 | 
			
		||||
				newFilter = Fir(obj)
 | 
			
		||||
			newFilter.adjust(self._prefix)
 | 
			
		||||
			if newFilter.id != self.prefix(iid):
 | 
			
		||||
				raise Exception("Invalid filter created %s" % (iid))
 | 
			
		||||
			self._filters[iid] = newFilter
 | 
			
		||||
 | 
			
		||||
		return self._filters[iid].sc3ID(self)
 | 
			
		||||
	
 | 
			
		||||
	def prefix(self, iid):
 | 
			
		||||
		if self._prefix:
 | 
			
		||||
			iid = "%s:%s" % (self._prefix, iid)
 | 
			
		||||
		return iid
 | 
			
		||||
 | 
			
		||||
	def dataloggerID(self, iid, gain = None):
 | 
			
		||||
		if iid not in self.keys:
 | 
			
		||||
			raise Exception("Object not found.")
 | 
			
		||||
 | 
			
		||||
		if iid not in self.dls:
 | 
			
		||||
			raise Exception("[%s] Object is not a datalogger" % iid)
 | 
			
		||||
 | 
			
		||||
		diid = self.instrumentId(iid, gain)
 | 
			
		||||
		
 | 
			
		||||
		if diid not in self._datalogger:
 | 
			
		||||
			## print >> sys.stderr,"Generating datalogger %s -> %s" % (iid, diid)
 | 
			
		||||
			newDatalogger = Dataloger(self.dls[iid], gain)
 | 
			
		||||
			newDatalogger.adjust(self._prefix)
 | 
			
		||||
			if newDatalogger.id != self.prefix(diid):
 | 
			
		||||
				raise Exception("Invalid datalogger created %s %s" % (iid, diid))
 | 
			
		||||
			self._datalogger[diid] = newDatalogger
 | 
			
		||||
		
 | 
			
		||||
		return self._datalogger[diid].sc3ID(self)
 | 
			
		||||
		
 | 
			
		||||
	def sensorID(self, iid, gain = None):
 | 
			
		||||
		if iid not in self.keys:
 | 
			
		||||
			raise Exception("Object not found.")
 | 
			
		||||
 | 
			
		||||
		if iid not in self.ses:
 | 
			
		||||
			raise Exception("[%s] Object is not a sensor" % iid)
 | 
			
		||||
 | 
			
		||||
		diid = self.instrumentId(iid, gain)
 | 
			
		||||
 | 
			
		||||
		if diid not in self._sensors:
 | 
			
		||||
			## print >> sys.stderr,"Generating Sensor %s -> %s" % (iid, diid)
 | 
			
		||||
			newSensor = Sensor(self.ses[iid], gain)
 | 
			
		||||
			newSensor.adjust(self._prefix)
 | 
			
		||||
			if newSensor.id != self.prefix(diid):
 | 
			
		||||
				raise Exception("Invalid sensor created %s %s" % (iid, diid))
 | 
			
		||||
			self._sensors[diid] = newSensor
 | 
			
		||||
		
 | 
			
		||||
		return self._sensors[diid].sc3ID(self)
 | 
			
		||||
 | 
			
		||||
	def _findObject(self, objID, where):
 | 
			
		||||
		obj = None
 | 
			
		||||
		for ob in where.values():
 | 
			
		||||
			obj = ob.sc3Obj(self)
 | 
			
		||||
			if obj.publicID() == objID:
 | 
			
		||||
				break;
 | 
			
		||||
		if not obj:
 | 
			
		||||
			raise Exception("Object not found: %s " % objID)
 | 
			
		||||
		return obj
 | 
			
		||||
 | 
			
		||||
	def _findCallibration(self, obj, count, serialNumber, channel, start):
 | 
			
		||||
		if serialNumber is None: 
 | 
			
		||||
			return None
 | 
			
		||||
		if channel is None:
 | 
			
		||||
			return None
 | 
			
		||||
		
 | 
			
		||||
		for cal in [obj(i) for i in range(0, count)]:
 | 
			
		||||
			if cal.serialNumber() == serialNumber and cal.channel() == channel:
 | 
			
		||||
				return cal.gain()
 | 
			
		||||
		return None
 | 
			
		||||
	
 | 
			
		||||
	def _sensorGain(self, seID, serialNumber, channel, start):
 | 
			
		||||
		sensor = self._findObject(seID, self._sensors)
 | 
			
		||||
		if not sensor: 
 | 
			
		||||
			raise Exception("Not found %s" % seID)
 | 
			
		||||
 | 
			
		||||
		sensorFilter = self._findObject(sensor.response(), self._filters)
 | 
			
		||||
		if not sensorFilter: 
 | 
			
		||||
			raise Exception("Not found %s" % seID)
 | 
			
		||||
 | 
			
		||||
		gainFrequency = sensorFilter.gainFrequency()
 | 
			
		||||
		try:
 | 
			
		||||
			gainUnit = sensor.unit()
 | 
			
		||||
		except:
 | 
			
		||||
			print("[%s] No gain unit supplied" % seID, file=sys.stderr)
 | 
			
		||||
			gainUnit = None
 | 
			
		||||
		
 | 
			
		||||
		gain = self._findCallibration(sensor.sensorCalibration, sensor.sensorCalibrationCount(), serialNumber, channel, start)
 | 
			
		||||
		if gain is not None:
 | 
			
		||||
			## print >> sys.stderr,'[%s] Using sensor gain from calibration %s' % (serialNumber, gain)
 | 
			
		||||
			pass
 | 
			
		||||
		else:
 | 
			
		||||
			gain = sensorFilter.gain()
 | 
			
		||||
		
 | 
			
		||||
		return (gain, gainFrequency, gainUnit)
 | 
			
		||||
	
 | 
			
		||||
	def _dataloggerGain(self, dtID, serialNumber, channel, Numerator, Denominator, start):
 | 
			
		||||
		datalogger = self._findObject(dtID, self._datalogger)
 | 
			
		||||
		gain = self._findCallibration(datalogger.dataloggerCalibration, datalogger.dataloggerCalibrationCount(), serialNumber, channel, start)
 | 
			
		||||
		if gain is not None:
 | 
			
		||||
			##print >> sys.stderr,'[%s] Using datalogger gain from calibration %s' % (serialNumber, gain)
 | 
			
		||||
			pass
 | 
			
		||||
		else:
 | 
			
		||||
			gain = datalogger.gain()
 | 
			
		||||
		
 | 
			
		||||
		decimation = None
 | 
			
		||||
		for i in range(0,datalogger.decimationCount()):
 | 
			
		||||
			decimation = datalogger.decimation(i)
 | 
			
		||||
			if decimation.sampleRateNumerator() == Numerator and decimation.sampleRateDenominator() == Denominator:
 | 
			
		||||
				break
 | 
			
		||||
			decimation = None
 | 
			
		||||
		
 | 
			
		||||
		if not decimation:
 | 
			
		||||
			raise Exception("Decimation not found %s/%s" % (Numerator, Denominator))
 | 
			
		||||
 | 
			
		||||
		af = decimation.analogueFilterChain().content().split()
 | 
			
		||||
		df = decimation.digitalFilterChain().content().split()
 | 
			
		||||
 | 
			
		||||
		for fiID in af:
 | 
			
		||||
			g = self._findObject(fiID, self._filters).gain()
 | 
			
		||||
			#print >> sys.stderr,"Multiplying by %s %s" % (fiID, g)
 | 
			
		||||
			gain = gain * g
 | 
			
		||||
 | 
			
		||||
		for fiID in df:
 | 
			
		||||
			g = self._findObject(fiID, self._filters).gain()
 | 
			
		||||
			#print >> sys.stderr,"Multiplying by %s %s" % (fiID, g)
 | 
			
		||||
			gain = gain * g
 | 
			
		||||
 | 
			
		||||
		return gain
 | 
			
		||||
 | 
			
		||||
	def getChannelGainAttribute(self, dtID, seID, dtSerialNumber, seSerialNumber, dtChannel, seChannel, Numerator, Denominator, channelStart):
 | 
			
		||||
		if not dtID or not seID:
 | 
			
		||||
			raise Exception("Empty instruments ID supplied.")
 | 
			
		||||
		
 | 
			
		||||
		(sensorGain, sensorFrequency,sensorUnit) = self._sensorGain(seID, seSerialNumber, seChannel, channelStart)
 | 
			
		||||
		dataloggerGain =  self._dataloggerGain(dtID, dtSerialNumber, dtChannel, Numerator, Denominator, channelStart)
 | 
			
		||||
		
 | 
			
		||||
		att = {}
 | 
			
		||||
		att['Gain'] = sensorGain * dataloggerGain
 | 
			
		||||
		if sensorFrequency is not None:
 | 
			
		||||
			att['GainFrequency'] = sensorFrequency
 | 
			
		||||
		if sensorUnit is not None:
 | 
			
		||||
			att['GainUnit'] = sensorUnit
 | 
			
		||||
		return att
 | 
			
		||||
 | 
			
		||||
class Paz(sc3, prefixable):
 | 
			
		||||
	def __init__(self, pz):
 | 
			
		||||
		sc3.__init__(self, 'paz')
 | 
			
		||||
		self.id = pz.id
 | 
			
		||||
		self.att = pz.getAttributes()
 | 
			
		||||
 | 
			
		||||
	def sc3Att(self):
 | 
			
		||||
		att = {}
 | 
			
		||||
		att['Name'] = self.id
 | 
			
		||||
 | 
			
		||||
		for (key,value) in self.att.items():
 | 
			
		||||
			if not self.sc3ValidKey(key) or key in att:
 | 
			
		||||
				print(" [%s] [%s] Ignoring Attribute %s = %s " % (self.sc3Mode, self.id, key,value), file=sys.stderr)
 | 
			
		||||
				continue
 | 
			
		||||
			att[key] = value
 | 
			
		||||
 | 
			
		||||
		return att
 | 
			
		||||
 | 
			
		||||
class Sensor(sc3, prefixable):
 | 
			
		||||
	def __init__(self, se, gain = None):
 | 
			
		||||
		sc3.__init__(self, 'sensor')
 | 
			
		||||
		self.baseid = se.id
 | 
			
		||||
		self.att = se.getAttributes()
 | 
			
		||||
 | 
			
		||||
		self.pz = se.generatePz(gain)
 | 
			
		||||
		
 | 
			
		||||
		self.id = "%s/g=%s" % (self.baseid, int(float(self.pz.gain)))
 | 
			
		||||
 | 
			
		||||
	def sc3Resolv(self, inventory):
 | 
			
		||||
		try:
 | 
			
		||||
			self.att['Response'] = inventory.filterID(self.pz.id)
 | 
			
		||||
			## print >> sys.stderr,"Re-used a sensor pole-zero"
 | 
			
		||||
		except:
 | 
			
		||||
			inventory.add(self.pz)
 | 
			
		||||
			self.att['Response'] = inventory.filterID(self.pz.id)
 | 
			
		||||
 | 
			
		||||
	def sc3Att(self):
 | 
			
		||||
		att = {}
 | 
			
		||||
		
 | 
			
		||||
		att['Name'] = self.id
 | 
			
		||||
		for (key, value) in self.att.items():
 | 
			
		||||
			if not self.sc3ValidKey(key) or key in att:
 | 
			
		||||
				print(" [%s] [%s] ignoring Attribute %s = %s " % (self.sc3Mode, self.id, key, value), file=sys.stderr)
 | 
			
		||||
				continue
 | 
			
		||||
			att[key] = value
 | 
			
		||||
 | 
			
		||||
		## Forcing needed description on the sensor
 | 
			
		||||
		if 'Description' not in att:
 | 
			
		||||
			att['Description'] = self.id
 | 
			
		||||
 | 
			
		||||
		return att
 | 
			
		||||
 | 
			
		||||
class Fir(sc3, prefixable):
 | 
			
		||||
	def __init__(self, ff):
 | 
			
		||||
		sc3.__init__(self, 'fir')
 | 
			
		||||
		self.id = ff.id
 | 
			
		||||
		self.gain = ff.gain
 | 
			
		||||
		self.att = ff.getAttributes()
 | 
			
		||||
 | 
			
		||||
	def sc3Att(self):
 | 
			
		||||
		att = {}
 | 
			
		||||
		att['Name'] = self.id
 | 
			
		||||
		
 | 
			
		||||
		for (key,value) in self.att.items():
 | 
			
		||||
			if not self.sc3ValidKey(key) or key in att :
 | 
			
		||||
				print(" [%s] [%s] Ignoring Attribute %s = %s " % (self.sc3Mode, self.id, key,value), file=sys.stderr)
 | 
			
		||||
				continue
 | 
			
		||||
			att[key] = value
 | 
			
		||||
		return att
 | 
			
		||||
	
 | 
			
		||||
class Decimation(sc3):
 | 
			
		||||
	def __init__(self, numerator, decimator, dl):
 | 
			
		||||
		sc3.__init__(self, 'decimation')
 | 
			
		||||
		self._numerator = numerator
 | 
			
		||||
		self._denominator = decimator
 | 
			
		||||
		self.chains = dl.chains[(numerator, decimator)]
 | 
			
		||||
		self.att = {}
 | 
			
		||||
 | 
			
		||||
	def sc3Resolv(self, inventory):
 | 
			
		||||
		sequence = {}
 | 
			
		||||
		sequence['A'] = []
 | 
			
		||||
		sequence['D'] = []
 | 
			
		||||
		
 | 
			
		||||
		
 | 
			
		||||
		for stage in self.chains:
 | 
			
		||||
			sid = inventory.filterID(stage)
 | 
			
		||||
			ADtype = inventory.filterType(stage)
 | 
			
		||||
			sequence[ADtype].append(sid)
 | 
			
		||||
 | 
			
		||||
		self.att['AnalogueFilterChain'] = " ".join(sequence['A'])
 | 
			
		||||
		self.att['DigitalFilterChain'] = " ".join(sequence['D'])
 | 
			
		||||
 | 
			
		||||
	def sc3Att(self):
 | 
			
		||||
		att = {}
 | 
			
		||||
		att['SampleRateNumerator'] = self._numerator
 | 
			
		||||
		att['SampleRateDenominator'] = self._denominator
 | 
			
		||||
		att.update(self.att)
 | 
			
		||||
		return att
 | 
			
		||||
 | 
			
		||||
class Dataloger(sc3, prefixable):
 | 
			
		||||
	def __init__(self, dl, gain = None):
 | 
			
		||||
		dcs = []
 | 
			
		||||
		sc3.__init__(self, 'datalogger', dcs)
 | 
			
		||||
		
 | 
			
		||||
		if gain:
 | 
			
		||||
			self.gain = gain
 | 
			
		||||
		else:
 | 
			
		||||
			self.gain = dl.gain
 | 
			
		||||
		
 | 
			
		||||
		self.att = dl.getAttributes()
 | 
			
		||||
		
 | 
			
		||||
		self.id = "%s/g=%s" % (dl.id, int(float(self.gain)))
 | 
			
		||||
		self.maxClockDrift = dl.mcld
 | 
			
		||||
		
 | 
			
		||||
		if dl.chains:
 | 
			
		||||
			for (num, dec) in dl.chains:
 | 
			
		||||
				dcs.append(Decimation(num, dec, dl))
 | 
			
		||||
			self.dcs = dcs
 | 
			
		||||
		else:
 | 
			
		||||
			print("[%s] Datalogger %s has no stages." % (self.id, dl), file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
	def sc3Att(self):
 | 
			
		||||
		att = {}
 | 
			
		||||
		att['Name'] = self.id
 | 
			
		||||
		att['Gain'] = self.gain
 | 
			
		||||
		att['MaxClockDrift'] = self.maxClockDrift
 | 
			
		||||
		
 | 
			
		||||
		for (key,value) in self.att.items():
 | 
			
		||||
			if not self.sc3ValidKey(key) or key in att:
 | 
			
		||||
				print(" [%s] [%s] ignoring Attribute %s = %s " % (self.sc3Mode, self.id, key, value), file=sys.stderr)
 | 
			
		||||
				continue
 | 
			
		||||
			att[key] = value
 | 
			
		||||
 | 
			
		||||
		## Forcing needed description on the sensor
 | 
			
		||||
		if 'Description' not in att:
 | 
			
		||||
			att['Description'] = self.id
 | 
			
		||||
 | 
			
		||||
		return att
 | 
			
		||||
 | 
			
		||||
class Calibration(sc3):
 | 
			
		||||
	def __init__(self, cl, channel, start, end):
 | 
			
		||||
		if cl.type == "S":
 | 
			
		||||
			sc3.__init__(self, "sensorCalibration")
 | 
			
		||||
		else:
 | 
			
		||||
			sc3.__init__(self, "dataloggerCalibration")
 | 
			
		||||
		
 | 
			
		||||
		if channel < 0 or channel >= cl.channelCount:
 | 
			
		||||
			raise Exception("Invalid channel for calibration [%s]" % channel)
 | 
			
		||||
 | 
			
		||||
		self.start = start
 | 
			
		||||
		self.end = end
 | 
			
		||||
		self.channel = channel
 | 
			
		||||
		self.id = cl.id
 | 
			
		||||
		self.att = cl.getAttributes(channel)
 | 
			
		||||
	
 | 
			
		||||
	def sc3Att(self):
 | 
			
		||||
		att = {}
 | 
			
		||||
		att['SerialNumber'] = self.id
 | 
			
		||||
		att['Start'] = self.start
 | 
			
		||||
		if self.end:
 | 
			
		||||
			att['End'] = self.end
 | 
			
		||||
 | 
			
		||||
		for (key, value) in self.att.items():
 | 
			
		||||
			if not self.sc3ValidKey(key) or key in att:
 | 
			
		||||
				print(" [%s] [%s] Ignoring Attribute %s = %s " % (self.sc3Mode, self.id, key,value), file=sys.stderr)
 | 
			
		||||
				continue
 | 
			
		||||
			att[key] = value
 | 
			
		||||
		return att
 | 
			
		||||
							
								
								
									
										489
									
								
								lib/python/nettab/nodesnslc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										489
									
								
								lib/python/nettab/nodesnslc.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,489 @@
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
from .lineType import Sl, Nw, Sr, Sg
 | 
			
		||||
from .nodesi import Instruments
 | 
			
		||||
from .basesc3 import sc3
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
debug = 0
 | 
			
		||||
 | 
			
		||||
class DontFit(Exception):
 | 
			
		||||
	def __init__(self, message):
 | 
			
		||||
		Exception.__init__(self, message)
 | 
			
		||||
 | 
			
		||||
class nslc(object):
 | 
			
		||||
	def __init__(self):
 | 
			
		||||
		self.start = None
 | 
			
		||||
		self.end = None
 | 
			
		||||
		self.code = None
 | 
			
		||||
 | 
			
		||||
	def __overlap__(self, another):
 | 
			
		||||
		if self.end:
 | 
			
		||||
			if self.end > another.start:
 | 
			
		||||
				if not another.end or self.start < another.end:
 | 
			
		||||
					return True
 | 
			
		||||
		else:
 | 
			
		||||
			if not another.end or self.start < another.end:
 | 
			
		||||
				return True
 | 
			
		||||
		return False
 | 
			
		||||
 | 
			
		||||
	def _span(self):
 | 
			
		||||
		return "%s / %s" % (self.start, self.end) 
 | 
			
		||||
 | 
			
		||||
	def sc3Att(self):
 | 
			
		||||
		att = {}
 | 
			
		||||
 | 
			
		||||
		att['Start'] = self.start
 | 
			
		||||
		if self.end:
 | 
			
		||||
			att['End'] =  self.end
 | 
			
		||||
		att['Code'] = self.code
 | 
			
		||||
 | 
			
		||||
		for (key,value) in self.att.items():
 | 
			
		||||
			if not self.sc3ValidKey(key) or key in att:
 | 
			
		||||
				print("[%s] type %s ignoring attribute %s = %s " % (self.code, self.sc3Mode, key,value), file=sys.stderr)
 | 
			
		||||
				continue
 | 
			
		||||
 | 
			
		||||
			att[key] = value
 | 
			
		||||
		return att
 | 
			
		||||
 | 
			
		||||
def _cmptime(t1, t2):
 | 
			
		||||
	if t1 is None and t2 is None:
 | 
			
		||||
		return 0
 | 
			
		||||
	elif t2 is None or (t1 is not None and t1 < t2):
 | 
			
		||||
		return -1
 | 
			
		||||
	elif t1 is None or (t2 is not None and t1 > t2):
 | 
			
		||||
		return 1
 | 
			
		||||
	return 0
 | 
			
		||||
 | 
			
		||||
class StationGroup(nslc,sc3):
 | 
			
		||||
	def __str__(self):
 | 
			
		||||
		return "%s" % (self.code)
 | 
			
		||||
 | 
			
		||||
	def __init__(self, sg):
 | 
			
		||||
		if not isinstance(sg,Sg):
 | 
			
		||||
			return False
 | 
			
		||||
 | 
			
		||||
		self.stationReferences = []
 | 
			
		||||
		sc3.__init__(self, 'stationGroup', self.stationReferences)
 | 
			
		||||
 | 
			
		||||
		self.code = sg.code
 | 
			
		||||
		self.start = sg.start
 | 
			
		||||
		self.end = sg.end
 | 
			
		||||
		self.att = sg.getStationGroupAttributes()
 | 
			
		||||
		self.srdata = []
 | 
			
		||||
 | 
			
		||||
	def __match__(self, sr):
 | 
			
		||||
		if not isinstance(sr,Sr):
 | 
			
		||||
			return False
 | 
			
		||||
		
 | 
			
		||||
		return (_cmptime(sr.start, self.end) <= 0 and _cmptime(sr.end, self.start) >= 0)
 | 
			
		||||
 | 
			
		||||
	def conflict(self, another):
 | 
			
		||||
		if self.code != another.code:
 | 
			
		||||
			return False
 | 
			
		||||
		
 | 
			
		||||
		if self.end:
 | 
			
		||||
			if self.end <= another.start:
 | 
			
		||||
				return False
 | 
			
		||||
			if another.end and another.end <= self.start:
 | 
			
		||||
				return False
 | 
			
		||||
		else:
 | 
			
		||||
			if another.end and another.end <= self.start:
 | 
			
		||||
				return False
 | 
			
		||||
		
 | 
			
		||||
		return True
 | 
			
		||||
 | 
			
		||||
	def Sr(self, sr):
 | 
			
		||||
		self.srdata.append((sr.ncode, sr.scode, sr.start, sr.end))
 | 
			
		||||
 | 
			
		||||
	def sc3Resolv(self, inventory):
 | 
			
		||||
		for (ncode, scode, start, end) in self.srdata:
 | 
			
		||||
			try:
 | 
			
		||||
				for stationID in inventory.resolveStation(ncode, scode, start, end):
 | 
			
		||||
					st = StationReference(self, stationID)
 | 
			
		||||
					self.stationReferences.append(st)
 | 
			
		||||
			except Exception as e:
 | 
			
		||||
				sys.stderr.write(str(e) + "\n")
 | 
			
		||||
 | 
			
		||||
class StationReference(sc3):
 | 
			
		||||
	def __str__(self):
 | 
			
		||||
		return "%s" % (self.att["StationID"])
 | 
			
		||||
 | 
			
		||||
	def __init__(self, stationGroup, stationID):
 | 
			
		||||
		self.stationGroup = stationGroup
 | 
			
		||||
		sc3.__init__(self, 'stationReference')
 | 
			
		||||
 | 
			
		||||
		self.att = { "StationID": stationID }
 | 
			
		||||
 | 
			
		||||
	def sc3Att(self):
 | 
			
		||||
		return self.att
 | 
			
		||||
 | 
			
		||||
class Network(nslc, sc3):
 | 
			
		||||
	def __str__(self):
 | 
			
		||||
		return "%s" % (self.code)
 | 
			
		||||
 | 
			
		||||
	def __init__(self, nw):
 | 
			
		||||
		if not isinstance(nw,Nw):
 | 
			
		||||
			return False
 | 
			
		||||
 | 
			
		||||
		self.stations = []
 | 
			
		||||
		sc3.__init__(self, 'network', self.stations)
 | 
			
		||||
 | 
			
		||||
		nslc.__init__(self)
 | 
			
		||||
		self.code = nw.code
 | 
			
		||||
		self.start = nw.start
 | 
			
		||||
		self.end = nw.end
 | 
			
		||||
		self.att = nw.getNetworkAttributes()
 | 
			
		||||
 | 
			
		||||
	def __match__(self, sl):
 | 
			
		||||
		if not isinstance(sl,Sl):
 | 
			
		||||
			return False
 | 
			
		||||
		
 | 
			
		||||
		if sl.start < self.start:
 | 
			
		||||
			return False
 | 
			
		||||
		if self.end:
 | 
			
		||||
			if not sl.end or sl.end > self.end:
 | 
			
		||||
				return False 
 | 
			
		||||
		return True
 | 
			
		||||
 | 
			
		||||
	def conflict(self, another):
 | 
			
		||||
		if self.code != another.code:
 | 
			
		||||
			return False
 | 
			
		||||
		
 | 
			
		||||
		if self.end:
 | 
			
		||||
			if self.end <= another.start:
 | 
			
		||||
				return False
 | 
			
		||||
			if another.end and another.end <= self.start:
 | 
			
		||||
				return False
 | 
			
		||||
		else:
 | 
			
		||||
			if another.end and another.end <= self.start:
 | 
			
		||||
				return False
 | 
			
		||||
		
 | 
			
		||||
		return True
 | 
			
		||||
 | 
			
		||||
	def Sl(self, sl):
 | 
			
		||||
		if not self.__match__(sl):
 | 
			
		||||
			raise DontFit(" Object doesn't fit this network object.")
 | 
			
		||||
		inserted = False
 | 
			
		||||
		for sta in self.stations:
 | 
			
		||||
			try:
 | 
			
		||||
				where = "%s" % (sta._span())
 | 
			
		||||
				sta.Sl(sl)
 | 
			
		||||
				if debug: print("[%s] inserted at %s -> %s" % (self, where, sta._span()), file=sys.stderr)
 | 
			
		||||
				inserted = True
 | 
			
		||||
				for other in self.stations:
 | 
			
		||||
					if other is sta: continue
 | 
			
		||||
					if other.conflict(sta):
 | 
			
		||||
						raise Exception("I Station conflict with already existing station (%s/%s/%s)" % (other, other.start, other.end))
 | 
			
		||||
				break
 | 
			
		||||
			except DontFit:
 | 
			
		||||
				pass
 | 
			
		||||
		if not inserted:
 | 
			
		||||
			st = Station(self, sl)
 | 
			
		||||
			if debug: print("[%s] created new station %s %s" % (self, st, st._span()), file=sys.stderr)
 | 
			
		||||
			for sta in self.stations:
 | 
			
		||||
				if sta.conflict(st):
 | 
			
		||||
					raise Exception("Station conflict with already existing station (%s/%s/%s)" % (sta, sta.start, sta.end))
 | 
			
		||||
			self.stations.append(st)
 | 
			
		||||
 | 
			
		||||
	def check(self, i):
 | 
			
		||||
		error = []
 | 
			
		||||
		for station in self.stations:
 | 
			
		||||
			error.extend(station.check(i))
 | 
			
		||||
		return error
 | 
			
		||||
	
 | 
			
		||||
	def use(self, iid):
 | 
			
		||||
		c = False
 | 
			
		||||
		for station in self.stations:
 | 
			
		||||
			c = c or station.use(iid)
 | 
			
		||||
			if c: break
 | 
			
		||||
		return c
 | 
			
		||||
 | 
			
		||||
class Station(nslc, sc3):
 | 
			
		||||
	def __str__(self):
 | 
			
		||||
		return "%s.%s" % (self.network.code, self.code)
 | 
			
		||||
 | 
			
		||||
	def __init__(self, network, sl):
 | 
			
		||||
		if not isinstance(sl,Sl):
 | 
			
		||||
			return False
 | 
			
		||||
 | 
			
		||||
		self.locations = []
 | 
			
		||||
		self.network = network
 | 
			
		||||
		sc3.__init__(self, 'station', self.locations)
 | 
			
		||||
 | 
			
		||||
		# I load myself as a station
 | 
			
		||||
		nslc.__init__(self)
 | 
			
		||||
		self.code = sl.code
 | 
			
		||||
		self.start = sl.start
 | 
			
		||||
		self.end = sl.end
 | 
			
		||||
		self.att = sl.getStationAttributes()
 | 
			
		||||
 | 
			
		||||
		# Further parse to generate my locations
 | 
			
		||||
		self.Sl(sl)
 | 
			
		||||
	
 | 
			
		||||
	def __match__(self, obj):
 | 
			
		||||
		if not isinstance(obj,Sl):
 | 
			
		||||
			return False
 | 
			
		||||
		# Check code
 | 
			
		||||
		if obj.code != self.code:
 | 
			
		||||
			return False
 | 
			
		||||
		# Attributes
 | 
			
		||||
		att = obj.getStationAttributes()
 | 
			
		||||
		for at in att:
 | 
			
		||||
			# Make sure that all attributes in Sl-line are here
 | 
			
		||||
			if at not in self.att:
 | 
			
		||||
				return False
 | 
			
		||||
			# And they match
 | 
			
		||||
			if att[at] != self.att[at]:
 | 
			
		||||
				return False
 | 
			
		||||
		# Make sure that there is no other attribute here that is not on Sl-line
 | 
			
		||||
		for at in self.att:
 | 
			
		||||
			if at not in att:
 | 
			
		||||
				return False
 | 
			
		||||
 | 
			
		||||
		return True
 | 
			
		||||
	
 | 
			
		||||
	def __adjustTime__(self, sl):
 | 
			
		||||
		if sl.start < self.start:
 | 
			
		||||
			self.start = sl.start
 | 
			
		||||
		if not self.end:
 | 
			
		||||
			return
 | 
			
		||||
		if sl.end and sl.end < self.end:
 | 
			
		||||
			return
 | 
			
		||||
		self.end = sl.end
 | 
			
		||||
 | 
			
		||||
	def conflict(self, another):
 | 
			
		||||
		if not isinstance(another, Station):
 | 
			
		||||
			raise Exception("Cannot compare myself with %s" % type(another))
 | 
			
		||||
		if self.code != another.code:
 | 
			
		||||
			return False
 | 
			
		||||
		if not self.__overlap__(another):
 | 
			
		||||
			return False
 | 
			
		||||
		return True
 | 
			
		||||
 | 
			
		||||
	def use(self, iid):
 | 
			
		||||
		c = False
 | 
			
		||||
		for location in self.locations:
 | 
			
		||||
			c = c or location.use(iid)
 | 
			
		||||
			if c: break
 | 
			
		||||
		return c
 | 
			
		||||
 | 
			
		||||
	def check(self, i):
 | 
			
		||||
		error = []
 | 
			
		||||
		for location in self.locations:
 | 
			
		||||
			error.extend(location.check(i))
 | 
			
		||||
		return error
 | 
			
		||||
 | 
			
		||||
	def Sl(self, sl):
 | 
			
		||||
		if not self.__match__(sl):
 | 
			
		||||
			raise DontFit(" sl doesn't fit this station %s/%s_%s." % (self.code, self.start, self.end))
 | 
			
		||||
		# Handle Time Adjustments
 | 
			
		||||
		self.__adjustTime__(sl)
 | 
			
		||||
		# Handle Locations
 | 
			
		||||
		inserted = False
 | 
			
		||||
		for loc in self.locations:
 | 
			
		||||
			try:
 | 
			
		||||
				where = loc._span()
 | 
			
		||||
				loc.Sl(sl)
 | 
			
		||||
				if debug: print(" [%s] inserted at %s -> %s" % (self, where, loc._span()), file=sys.stderr)
 | 
			
		||||
				inserted = True
 | 
			
		||||
				for other in self.locations:
 | 
			
		||||
					if other is loc: continue
 | 
			
		||||
					if other.conflict(loc):
 | 
			
		||||
						raise Exception("Location conflict with already existing location")
 | 
			
		||||
				break
 | 
			
		||||
			except DontFit:
 | 
			
		||||
				pass
 | 
			
		||||
		
 | 
			
		||||
		if not inserted:
 | 
			
		||||
			loc = Location(self, sl)
 | 
			
		||||
			if debug: print(" [%s] created new location %s %s" % (self, loc, loc._span()), file=sys.stderr)
 | 
			
		||||
			for lc in self.locations:
 | 
			
		||||
				if lc.conflict(loc):
 | 
			
		||||
					raise Exception("Location conflict with already existing location")
 | 
			
		||||
			self.locations.append(loc)
 | 
			
		||||
 | 
			
		||||
	def sc3Att(self):
 | 
			
		||||
		att = nslc.sc3Att(self)
 | 
			
		||||
		
 | 
			
		||||
		## Make sure that we set the Remark
 | 
			
		||||
		if 'ArchiveNetworkCode' not in att:
 | 
			
		||||
			att['ArchiveNetworkCode'] = self.network.code
 | 
			
		||||
 | 
			
		||||
		if 'Remark' not in att:
 | 
			
		||||
			att['Remark'] = ""
 | 
			
		||||
		return att
 | 
			
		||||
 | 
			
		||||
class Location(nslc, sc3):
 | 
			
		||||
	def __str__(self):
 | 
			
		||||
		return "%s.%s.%s" % (self.station.network.code, self.station.code, self.code)
 | 
			
		||||
 | 
			
		||||
	def __init__(self, station, sl):
 | 
			
		||||
		if not isinstance(sl, Sl):
 | 
			
		||||
			return False
 | 
			
		||||
		self.channels = []
 | 
			
		||||
		sc3.__init__(self, 'location', self.channels)
 | 
			
		||||
		
 | 
			
		||||
		nslc.__init__(self)
 | 
			
		||||
		self.station = station
 | 
			
		||||
		self.code = sl.location
 | 
			
		||||
		self.start = sl.start
 | 
			
		||||
		self.end = sl.end
 | 
			
		||||
		self.att = sl.getLocationAttributes()
 | 
			
		||||
		self.Sl(sl)
 | 
			
		||||
	
 | 
			
		||||
	def __adjustTime__(self, sl):
 | 
			
		||||
		if sl.start < self.start:
 | 
			
		||||
			self.start = sl.start
 | 
			
		||||
		if not self.end:
 | 
			
		||||
			return
 | 
			
		||||
		if sl.end and sl.end < self.end:
 | 
			
		||||
			return
 | 
			
		||||
		self.end = sl.end
 | 
			
		||||
	
 | 
			
		||||
	def __match__(self, obj):
 | 
			
		||||
		if not isinstance(obj, Sl):
 | 
			
		||||
			return False
 | 
			
		||||
		if obj.location != self.code:
 | 
			
		||||
			return False
 | 
			
		||||
		# Attributes
 | 
			
		||||
		att = obj.getLocationAttributes()
 | 
			
		||||
		for at in att:
 | 
			
		||||
			# Make sure that all attributes in Sl-line are here
 | 
			
		||||
			if at not in self.att:
 | 
			
		||||
				return False
 | 
			
		||||
			# And they match
 | 
			
		||||
			if att[at] != self.att[at]:
 | 
			
		||||
				return False
 | 
			
		||||
		# Make sure that there is no other attribute here that is not on Sl-line
 | 
			
		||||
		for at in self.att:
 | 
			
		||||
			if at not in att:
 | 
			
		||||
				return False
 | 
			
		||||
		return True
 | 
			
		||||
 | 
			
		||||
	def conflict(self, another):
 | 
			
		||||
		if not isinstance(another, Location):
 | 
			
		||||
			raise Exception("Cannot compare myself with %s" % type(another))
 | 
			
		||||
		if self.code != another.code:
 | 
			
		||||
			return False
 | 
			
		||||
		if not self.__overlap__(another):
 | 
			
		||||
			return False
 | 
			
		||||
		return True
 | 
			
		||||
 | 
			
		||||
	def use(self, iid):
 | 
			
		||||
		c = False
 | 
			
		||||
		for channel in self.channels:
 | 
			
		||||
			c = c or channel.use(iid)
 | 
			
		||||
			if c: break
 | 
			
		||||
		return c
 | 
			
		||||
 | 
			
		||||
	def check(self, i):
 | 
			
		||||
		error = []
 | 
			
		||||
		for channel in self.channels:
 | 
			
		||||
			error.extend(channel.check(i))
 | 
			
		||||
		return error
 | 
			
		||||
 | 
			
		||||
	def Sl(self, sl):
 | 
			
		||||
		if not self.__match__(sl):
 | 
			
		||||
			raise DontFit(" This obj doesn't match this Location '%s'" % self.code)
 | 
			
		||||
		
 | 
			
		||||
		# Handle Time Adjustments
 | 
			
		||||
		self.__adjustTime__(sl)
 | 
			
		||||
		
 | 
			
		||||
		# Create Channels
 | 
			
		||||
		for code in sl.channels:
 | 
			
		||||
			channel = (Channel(self, code, sl))
 | 
			
		||||
			if debug: print("  [%s] created new channel %s/%s" % (self, channel, channel._span()), file=sys.stderr)
 | 
			
		||||
			for echan in self.channels:
 | 
			
		||||
				if echan.conflict(channel):
 | 
			
		||||
					raise Exception("[%s] channel %s conflict with already existing channel" % (self, code))
 | 
			
		||||
			#print >>sys.stderr," Channel %s appended at '%s'" % (code, self.code)
 | 
			
		||||
			self.channels.append(channel)
 | 
			
		||||
 | 
			
		||||
class Channel(nslc, sc3):
 | 
			
		||||
	def __str__(self):
 | 
			
		||||
		return "%s.%s.%s.%s" % (self.location.station.network.code, self.location.station.code, self.location.code, self.code)
 | 
			
		||||
 | 
			
		||||
	def __init__(self, location, code, sl):
 | 
			
		||||
		sc3.__init__(self, 'channel')
 | 
			
		||||
		self.location = location
 | 
			
		||||
 | 
			
		||||
		nslc.__init__(self)
 | 
			
		||||
		self.code = code
 | 
			
		||||
		self.start = sl.start
 | 
			
		||||
		self.end = sl.end
 | 
			
		||||
		self.att = sl.getChannelAttributes(self.code)
 | 
			
		||||
		
 | 
			
		||||
		## Bring the Instrument gains to the channel level
 | 
			
		||||
		self._sensorGain = sl.sensorGain
 | 
			
		||||
		self._dataloggerGain = sl.dataloggerGain
 | 
			
		||||
	
 | 
			
		||||
	def conflict(self, another):
 | 
			
		||||
		if not isinstance(another, Channel):
 | 
			
		||||
			raise Exception("Cannot compare myself with %s" % type(another))
 | 
			
		||||
		if self.code != another.code:
 | 
			
		||||
			return False
 | 
			
		||||
		if not self.__overlap__(another):
 | 
			
		||||
			return False
 | 
			
		||||
		return True
 | 
			
		||||
 | 
			
		||||
	def use(self, iid):
 | 
			
		||||
		if 'Datalogger' in self.att and iid == self.att['Datalogger']: return True
 | 
			
		||||
		if 'Sesor' in self.att and iid == self.att['Sensor']: return True
 | 
			
		||||
		return False
 | 
			
		||||
 | 
			
		||||
	def check(self, i):
 | 
			
		||||
		good = []
 | 
			
		||||
		
 | 
			
		||||
		if not isinstance(i, Instruments):
 | 
			
		||||
			raise Exception("Invalid instrument object")
 | 
			
		||||
 | 
			
		||||
		if not self.att['Datalogger'] in i.keys:
 | 
			
		||||
			good.append("no Datalogger") 
 | 
			
		||||
			
 | 
			
		||||
		if not self.att['Sensor'] in i.keys:
 | 
			
		||||
			good.append("no Sensor") 
 | 
			
		||||
		
 | 
			
		||||
		if good:
 | 
			
		||||
			good = [ " [%s] %s" % (self, "/".join(good)) ]
 | 
			
		||||
		
 | 
			
		||||
		return good
 | 
			
		||||
 | 
			
		||||
	def sc3Resolv(self, inventory):
 | 
			
		||||
		if not inventory:
 | 
			
		||||
			print("[%s] Warning, inventory not supplied" % self.code, file=sys.stderr) 
 | 
			
		||||
			return
 | 
			
		||||
	
 | 
			
		||||
		try:
 | 
			
		||||
			ssm = self.att['Sensor']
 | 
			
		||||
			ssg = self._sensorGain
 | 
			
		||||
			sch = self.att['SensorChannel']
 | 
			
		||||
			ssn = self.att["SensorSerialNumber"] if "SensorSerialNumber" in self.att else None
 | 
			
		||||
			# Sensor publicID
 | 
			
		||||
			ss = inventory.sensorID(ssm, ssg)
 | 
			
		||||
			self.att['Sensor'] = ss
 | 
			
		||||
			
 | 
			
		||||
			# Sensor Calibration
 | 
			
		||||
			inventory.loadSensorCalibrations(ssm, ssn, sch, ssg, self.start, self.end, ss)
 | 
			
		||||
		except Exception as e:
 | 
			
		||||
			print("[%s] Sensor Resolution Error %s" % (self, e), file=sys.stderr)
 | 
			
		||||
			ss = None
 | 
			
		||||
		
 | 
			
		||||
		try:
 | 
			
		||||
			dsm = self.att['Datalogger']
 | 
			
		||||
			dsg = self._dataloggerGain
 | 
			
		||||
			dch = self.att['DataloggerChannel']
 | 
			
		||||
			dsn = self.att['DataloggerSerialNumber'] if 'DataloggerSerialNumber' in self.att else None
 | 
			
		||||
 | 
			
		||||
			dt = inventory.dataloggerID(dsm, dsg)
 | 
			
		||||
			self.att['Datalogger'] = dt
 | 
			
		||||
			inventory.loadDataloggerCalibrations(dsm, dsn, dch, dsg, self.start, self.end, dt)
 | 
			
		||||
		except Exception as e:
 | 
			
		||||
			print("[%s] Datalogger Resolution Error %s" % (self, e), file=sys.stderr)
 | 
			
		||||
			dt = None
 | 
			
		||||
		
 | 
			
		||||
		try:
 | 
			
		||||
			up = self.att['SampleRateNumerator']
 | 
			
		||||
			down = self.att['SampleRateDenominator']
 | 
			
		||||
			self.att.update(inventory.getChannelGainAttribute(dt, ss, dsn, ssn, dch, sch, up, down, self.start))
 | 
			
		||||
		except Exception as e:
 | 
			
		||||
			print("[%s] Cannot find gain back for the channel: %s" % (self,e), file=sys.stderr)
 | 
			
		||||
							
								
								
									
										65
									
								
								lib/python/nettab/stationResolver.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								lib/python/nettab/stationResolver.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,65 @@
 | 
			
		||||
import time, datetime
 | 
			
		||||
 | 
			
		||||
def _cmptime(t1, t2):
 | 
			
		||||
	if t1 is None and t2 is None:
 | 
			
		||||
		return 0
 | 
			
		||||
	elif t2 is None or (t1 is not None and t1 < t2):
 | 
			
		||||
		return -1
 | 
			
		||||
	elif t1 is None or (t2 is not None and t1 > t2):
 | 
			
		||||
		return 1
 | 
			
		||||
	return 0
 | 
			
		||||
 | 
			
		||||
def _time2datetime(t):
 | 
			
		||||
	result = datetime.datetime(*time.strptime(t.toString("%Y-%m-%dT%H:%M:00Z"), "%Y-%m-%dT%H:%M:%SZ")[0:6])
 | 
			
		||||
	result += datetime.timedelta(microseconds=float(t.toString("%S.%f")) * 1000000)
 | 
			
		||||
 | 
			
		||||
class StationResolver(object):
 | 
			
		||||
	def __init__(self):
 | 
			
		||||
		self.stationMap = {}
 | 
			
		||||
		self.initialStations = set()
 | 
			
		||||
 | 
			
		||||
	def collectStations(self, inventory, initial = False):
 | 
			
		||||
		for ni in range(inventory.networkCount()):
 | 
			
		||||
			n = inventory.network(ni)
 | 
			
		||||
			for si in range(n.stationCount()):
 | 
			
		||||
				s = n.station(si)
 | 
			
		||||
 | 
			
		||||
				try:
 | 
			
		||||
					if initial:
 | 
			
		||||
						self.initialStations.add((n.code(), s.code()))
 | 
			
		||||
 | 
			
		||||
					else:
 | 
			
		||||
						self.initialStations.remove((n.code(), s.code()))
 | 
			
		||||
						del self.stationMap[(n.code(), s.code())]
 | 
			
		||||
 | 
			
		||||
				except KeyError:
 | 
			
		||||
					pass
 | 
			
		||||
 | 
			
		||||
				try:
 | 
			
		||||
					item = self.stationMap[(n.code(), s.code())]
 | 
			
		||||
 | 
			
		||||
				except KeyError:
 | 
			
		||||
					item = []
 | 
			
		||||
					self.stationMap[(n.code(), s.code())] = item
 | 
			
		||||
				
 | 
			
		||||
				start = _time2datetime(s.start())
 | 
			
		||||
				try: end = _time2datetime(s.end())
 | 
			
		||||
				except: end = None
 | 
			
		||||
 | 
			
		||||
				item.append((start, end, s.publicID()))
 | 
			
		||||
 | 
			
		||||
	def resolveStation(self, ncode, scode, start, end):
 | 
			
		||||
		result = set()
 | 
			
		||||
		try:
 | 
			
		||||
			for (s, e, publicID) in self.stationMap[(ncode, scode)]:
 | 
			
		||||
				if _cmptime(start, e) <= 0 and _cmptime(end, s) >= 0:
 | 
			
		||||
					result.add(publicID)
 | 
			
		||||
 | 
			
		||||
		except KeyError:
 | 
			
		||||
			pass
 | 
			
		||||
 | 
			
		||||
		if not result:
 | 
			
		||||
			raise Exception("Station reference %s,%s cannot be resolved" % (ncode, scode))
 | 
			
		||||
 | 
			
		||||
		return result
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										364
									
								
								lib/python/nettab/tab.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										364
									
								
								lib/python/nettab/tab.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,364 @@
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
from .lineType import Nw, Sg, Sr, Sl, Sa, Na, Dl, Se, Ff, Pz, Ia, Cl
 | 
			
		||||
from .nodesi import Instruments
 | 
			
		||||
from .nodesnslc import Network, StationGroup, DontFit
 | 
			
		||||
import seiscomp.datamodel, seiscomp.io, seiscomp.client
 | 
			
		||||
from .stationResolver import StationResolver
 | 
			
		||||
import sys
 | 
			
		||||
import os
 | 
			
		||||
import glob
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
__VERSION__ = "0.1"
 | 
			
		||||
 | 
			
		||||
class Tab(object):
 | 
			
		||||
	def version(self):
 | 
			
		||||
		return __VERSION__
 | 
			
		||||
 | 
			
		||||
	def __init__(self, instrumentPrefix = None, defaultsFile = None, filterFolder = None, xmlFolder = None, database = None):
 | 
			
		||||
		self.i = Instruments(instrumentPrefix)
 | 
			
		||||
		self.n = {}
 | 
			
		||||
		self.g = {}
 | 
			
		||||
		self.sas = []
 | 
			
		||||
		self.nas = []
 | 
			
		||||
		self.ias = []
 | 
			
		||||
		self.stationResolver = StationResolver()
 | 
			
		||||
		
 | 
			
		||||
		self._filterFolder = None
 | 
			
		||||
		
 | 
			
		||||
		print("Starting tab2inv version %s" % self.version(), file=sys.stderr)
 | 
			
		||||
		
 | 
			
		||||
		if not filterFolder:
 | 
			
		||||
			print(" Warning, not filter folder supplied.", file=sys.stderr)
 | 
			
		||||
		else:  
 | 
			
		||||
			if not os.path.isdir(filterFolder):
 | 
			
		||||
				raise Exception("Filter folder does not exist.")
 | 
			
		||||
 | 
			
		||||
		self._filterFolder = filterFolder
 | 
			
		||||
 | 
			
		||||
		if defaultsFile is not None:
 | 
			
		||||
			self._defaults(defaultsFile)
 | 
			
		||||
 | 
			
		||||
		if database is not None:
 | 
			
		||||
			self._loadDatabase(database)
 | 
			
		||||
	
 | 
			
		||||
		if xmlFolder is not None:
 | 
			
		||||
			self._loadXml(xmlFolder)
 | 
			
		||||
 | 
			
		||||
	def _defaults(self, filename):
 | 
			
		||||
		sas = []
 | 
			
		||||
		ias = []
 | 
			
		||||
		nas = []
 | 
			
		||||
		try:
 | 
			
		||||
			fd = open(filename)
 | 
			
		||||
			print(" Parsing defaults file: %s" % (filename), file=sys.stderr)
 | 
			
		||||
			for line in fd:
 | 
			
		||||
				line = line.strip()
 | 
			
		||||
				if not line or line[0] == "#": continue
 | 
			
		||||
				(Type, Content) = line.split(":",1)
 | 
			
		||||
				if Type == "Nw":
 | 
			
		||||
					raise Exception("Defaults file can only contain attributes")
 | 
			
		||||
				elif Type == "Na":
 | 
			
		||||
					nas.append(Na(Content))
 | 
			
		||||
				elif Type == "Sa":
 | 
			
		||||
					sas.append(Sa(Content))
 | 
			
		||||
				elif Type == "Sl":
 | 
			
		||||
					raise Exception("Defaults file can only contain attributes")
 | 
			
		||||
				elif Type == "Ia":
 | 
			
		||||
					ias.append(Ia(Content))
 | 
			
		||||
				elif Type == "Se":
 | 
			
		||||
					raise Exception("Defaults file can only contain attributes")
 | 
			
		||||
				elif Type == "Dl":
 | 
			
		||||
					raise Exception("Defaults file can only contain attributes")
 | 
			
		||||
				elif Type == "Cl":
 | 
			
		||||
					raise Exception("Defaults file can only contain attributes")
 | 
			
		||||
				elif Type == "Ff":
 | 
			
		||||
					raise Exception("Defaults file can only contain attributes")
 | 
			
		||||
				elif Type == "If":
 | 
			
		||||
					raise Exception("Defaults file can only contain attributes")
 | 
			
		||||
				elif Type == "Pz":
 | 
			
		||||
					raise Exception("Defaults file can only contain attributes")
 | 
			
		||||
				else:
 | 
			
		||||
					print(" Ignored line", line, file=sys.stderr)
 | 
			
		||||
			fd.close()
 | 
			
		||||
		except Exception as e:
 | 
			
		||||
			print(" Warning: %s" % e, file=sys.stderr)
 | 
			
		||||
			pass
 | 
			
		||||
		
 | 
			
		||||
		self.sas = sas
 | 
			
		||||
		self.nas = nas
 | 
			
		||||
		self.ias = ias
 | 
			
		||||
 | 
			
		||||
	def _loadDatabase(self, dbUrl):
 | 
			
		||||
		m = re.match("(?P<dbDriverName>^.*):\/\/(?P<dbAddress>.+?:.+?@.+?\/.+$)", dbUrl)
 | 
			
		||||
		if not m:
 | 
			
		||||
			raise Exception("error in parsing SC3 DB url")
 | 
			
		||||
		
 | 
			
		||||
		db = m.groupdict()
 | 
			
		||||
		
 | 
			
		||||
		try:
 | 
			
		||||
			registry = seiscomp.system.PluginRegistry.Instance()
 | 
			
		||||
			registry.addPluginName("dbmysql")
 | 
			
		||||
			registry.loadPlugins()
 | 
			
		||||
		except Exception as e:
 | 
			
		||||
			raise #"Cannot load database driver: %s"
 | 
			
		||||
		
 | 
			
		||||
		dbDriver = seiscomp.io.DatabaseInterface.Create(db["dbDriverName"])
 | 
			
		||||
		if dbDriver is None:
 | 
			
		||||
			raise Exception("Cannot find database driver " + db["dbDriverName"])
 | 
			
		||||
 | 
			
		||||
		if not dbDriver.connect(db["dbAddress"]):
 | 
			
		||||
			raise Exception("Cannot connect to database at " + db["dbAddress"])
 | 
			
		||||
 | 
			
		||||
		dbQuery = seiscomp.datamodel.DatabaseQuery(dbDriver)
 | 
			
		||||
		if dbQuery is None:
 | 
			
		||||
			raise Exception("Cannot get DB query object")
 | 
			
		||||
 | 
			
		||||
		print(" Loading inventory from database ... ", end=' ', file=sys.stderr)
 | 
			
		||||
		inventory = seiscomp.datamodel.Inventory()
 | 
			
		||||
		dbQuery.loadNetworks(inventory)
 | 
			
		||||
		for ni in range(inventory.networkCount()):
 | 
			
		||||
			dbQuery.loadStations(inventory.network(ni))
 | 
			
		||||
		print("Done.", file=sys.stderr)
 | 
			
		||||
		if inventory:
 | 
			
		||||
			self.stationResolver.collectStations(inventory, True)
 | 
			
		||||
 | 
			
		||||
	def _loadXml(self, folder):
 | 
			
		||||
		print(" Loading inventory from XML file ... ", end=' ', file=sys.stderr)
 | 
			
		||||
		for f in glob.glob(os.path.join(folder, "*.xml")):
 | 
			
		||||
			ar = seiscomp.io.XMLArchive()
 | 
			
		||||
			ar.open(f)
 | 
			
		||||
			inventory = seiscomp.datamodel.Inventory_Cast(ar.readObject())
 | 
			
		||||
			ar.close()
 | 
			
		||||
 | 
			
		||||
			if inventory:
 | 
			
		||||
				self.stationResolver.collectStations(inventory)
 | 
			
		||||
		print("Done.", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
	def digest(self, tabFilename):
 | 
			
		||||
		sas = []
 | 
			
		||||
		ias = []
 | 
			
		||||
		nw = None
 | 
			
		||||
		
 | 
			
		||||
		n = None
 | 
			
		||||
		g = None
 | 
			
		||||
		print(" Parsing file: %s" % (tabFilename), file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
		if not tabFilename or not os.path.isfile(tabFilename):
 | 
			
		||||
			raise Exception("Supplied filename is invalid.")
 | 
			
		||||
		
 | 
			
		||||
		if tabFilename in list(self.n.keys()) or tabFilename in list(self.g.keys()):
 | 
			
		||||
			raise Exception("File %s is already digested." % tabFilename)
 | 
			
		||||
		filename = 1
 | 
			
		||||
		try:
 | 
			
		||||
			fd = open(tabFilename)
 | 
			
		||||
			for line in fd:
 | 
			
		||||
				obj = None
 | 
			
		||||
				line = line.strip()
 | 
			
		||||
				if not line or line[0] == "#": continue
 | 
			
		||||
				if str(line).find(":") == -1:
 | 
			
		||||
					raise Exception("Invalid line format '%s'" % line)
 | 
			
		||||
				(Type, Content) = line.split(":",1)
 | 
			
		||||
 | 
			
		||||
				if Type == "Nw":
 | 
			
		||||
					if n or g:
 | 
			
		||||
						raise Exception("Network or Station Group already defined, only one Hr line should be defined per file.")
 | 
			
		||||
					try:
 | 
			
		||||
						nw = Nw(Content)
 | 
			
		||||
					except Exception as e:
 | 
			
		||||
						raise Exception("Error while creating nw from '%s': %s" % (Content, e))
 | 
			
		||||
					try:
 | 
			
		||||
						for na in self.nas:	nw.Na(na) # Defaults
 | 
			
		||||
					except Exception as e:
 | 
			
		||||
						raise Exception("Error while loading (defaults) %s into %s: %s" % (na, nw, e))
 | 
			
		||||
 | 
			
		||||
				elif Type == "Sg":
 | 
			
		||||
					if n or g:
 | 
			
		||||
						raise Exception("Network or Station Group already defined, only one Hr line should be defined per file.")
 | 
			
		||||
 | 
			
		||||
					try:
 | 
			
		||||
						sg = Sg(Content)
 | 
			
		||||
					except Exception as e:
 | 
			
		||||
						raise Exception("Error while creating sg from '%s': %s" % (Content, e))
 | 
			
		||||
					try:
 | 
			
		||||
						for na in self.nas:	sg.Na(na) # Defaults
 | 
			
		||||
					except Exception as e:
 | 
			
		||||
						raise Exception("Error while loading (defaults) %s into %s: %s" % (na, sg, e))
 | 
			
		||||
 | 
			
		||||
				elif Type == "Na":
 | 
			
		||||
					if not nw and not sg:
 | 
			
		||||
						raise Exception("No network defined, no Na line before a Hr line.")
 | 
			
		||||
					if n or g:
 | 
			
		||||
						raise Exception("No Na lines after a Sl line. Network has already been defined.")
 | 
			
		||||
					try:
 | 
			
		||||
						na = Na(Content)
 | 
			
		||||
					except Exception as e:
 | 
			
		||||
						raise Exception("Error while creating na from '%s': %s" % (Content, e))
 | 
			
		||||
					if nw:
 | 
			
		||||
						try:
 | 
			
		||||
							nw.Na(na)
 | 
			
		||||
						except Exception as e:
 | 
			
		||||
							raise Exception("Error while adding %s to %s: %s" % (na, nw, e))
 | 
			
		||||
					else:
 | 
			
		||||
						try:
 | 
			
		||||
							sg.Na(na)
 | 
			
		||||
						except Exception as e:
 | 
			
		||||
							raise Exception("Error while adding %s to %s: %s" % (na, sg, e))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
				elif Type == "Sa":
 | 
			
		||||
					if not nw:
 | 
			
		||||
						raise Exception("Not Sa line before a hr line allowed.")
 | 
			
		||||
					try:
 | 
			
		||||
						sas.append(Sa(Content))
 | 
			
		||||
					except Exception as e:
 | 
			
		||||
						raise Exception("Error while creating Sa from '%s': %s" % (Content,e))
 | 
			
		||||
 | 
			
		||||
				elif Type == "Sl":
 | 
			
		||||
					if not n:
 | 
			
		||||
						if not nw:
 | 
			
		||||
							raise Exception("No network defined, Hr line should come before station line.")
 | 
			
		||||
						else:
 | 
			
		||||
							n = Network(nw)
 | 
			
		||||
							for (filename, network) in self.n.items():
 | 
			
		||||
								if network.conflict(n):
 | 
			
		||||
									raise Exception("Network already defined %s (%s)-(%s) by file %s." % (network.code, network.start, network.end, filename))
 | 
			
		||||
					try:
 | 
			
		||||
						sl = Sl(Content)
 | 
			
		||||
					except Exception as e:
 | 
			
		||||
						raise Exception("Error while creating sl from '%s': %s" % (Content, e))
 | 
			
		||||
					# Fill in attributes
 | 
			
		||||
					try:
 | 
			
		||||
						for sa in self.sas: sl.Sa(sa) # Defaults
 | 
			
		||||
					except Exception as e:
 | 
			
		||||
						raise Exception("Error while loading (default) %s into %s: %s" % (sa, sl, e))
 | 
			
		||||
					try:
 | 
			
		||||
						for sa in sas: sl.Sa(sa) # Collected
 | 
			
		||||
					except Exception as e:
 | 
			
		||||
						raise Exception("Error while loading %s into %s: %s" % (str(sa), str(sl), e))
 | 
			
		||||
					# Digest by Station
 | 
			
		||||
					try:
 | 
			
		||||
						n.Sl(sl)
 | 
			
		||||
					except DontFit:
 | 
			
		||||
						raise Exception("%s does not fit in %s" % (sl, n))
 | 
			
		||||
					except Exception as e:
 | 
			
		||||
						raise Exception("Error while loading %s into %s: %s" % (sl, n, e))
 | 
			
		||||
 | 
			
		||||
				elif Type == "Sr":
 | 
			
		||||
					if not g:
 | 
			
		||||
						if not sg:
 | 
			
		||||
							raise Exception("No station group defined, Sg line should come before station reference line.")
 | 
			
		||||
						else:
 | 
			
		||||
							g = StationGroup(sg)
 | 
			
		||||
							for (filename, stationGroup) in self.g.items():
 | 
			
		||||
								if stationGroup.conflict(g):
 | 
			
		||||
									raise Exception("Station group already defined %s (%s)-(%s) by file %s." % (stationGroup.code, stationGroup.start, stationGroup.end, filename))
 | 
			
		||||
							for (filename, network) in self.n.items():
 | 
			
		||||
								if network.conflict(g):
 | 
			
		||||
									raise Exception("Station group conflict network already defined %s (%s)-(%s) by file %s." % (network.code, network.start, network.end, filename))
 | 
			
		||||
 | 
			
		||||
					try:
 | 
			
		||||
						sr = Sr(Content)
 | 
			
		||||
					except Exception as e:
 | 
			
		||||
						raise Exception("Error while creating sr from '%s': %s" % (Content, e))
 | 
			
		||||
					# Digest by Station Reference
 | 
			
		||||
					try:
 | 
			
		||||
						g.Sr(sr)
 | 
			
		||||
					except DontFit:
 | 
			
		||||
						raise Exception("%s does not fit in %s" % (sr, n))
 | 
			
		||||
					except Exception as e:
 | 
			
		||||
						raise Exception("Error while loading %s into %s: %s" % (sr, n, e))
 | 
			
		||||
 | 
			
		||||
				elif Type == "Ia":
 | 
			
		||||
					ias.append(Ia(Content))
 | 
			
		||||
 | 
			
		||||
				elif Type == "Se":
 | 
			
		||||
					obj = Se(Content)
 | 
			
		||||
 | 
			
		||||
				elif Type == "Dl":
 | 
			
		||||
					obj = Dl(Content)
 | 
			
		||||
 | 
			
		||||
				elif Type == "Cl":
 | 
			
		||||
					obj = Cl(Content)
 | 
			
		||||
 | 
			
		||||
				elif Type == "Ff":
 | 
			
		||||
					obj = Ff(self._filterFolder, Content)
 | 
			
		||||
 | 
			
		||||
				elif Type == "If":
 | 
			
		||||
					obj = Pz(Content,'D')
 | 
			
		||||
 | 
			
		||||
				elif Type == "Pz":
 | 
			
		||||
					obj = Pz(Content,'A')
 | 
			
		||||
				else:
 | 
			
		||||
					print(" Ignored line", line, file=sys.stderr)
 | 
			
		||||
	
 | 
			
		||||
				## Process Instrument
 | 
			
		||||
				if obj:
 | 
			
		||||
					try:
 | 
			
		||||
						for ia in self.ias: obj.Ia(ia) # Defaults
 | 
			
		||||
					except Exception as e:
 | 
			
		||||
						raise Exception("Error while loading (defaults) %s into %s: %s" % (ia, obj, e)) 
 | 
			
		||||
					try:
 | 
			
		||||
						for ia in ias: obj.Ia(ia) # Collected
 | 
			
		||||
					except Exception as e:
 | 
			
		||||
						raise Exception("Error while loading %s into %s: %s" % (ia, obj, e)) 
 | 
			
		||||
					try:
 | 
			
		||||
						self.i.add(obj)
 | 
			
		||||
					except Exception as e:
 | 
			
		||||
						raise Exception("Error while loading %s into Instruments db: %s" % (obj, e))
 | 
			
		||||
					obj = None
 | 
			
		||||
	
 | 
			
		||||
			# Process Network
 | 
			
		||||
			if n:
 | 
			
		||||
				self.n[tabFilename] = n
 | 
			
		||||
 | 
			
		||||
			# Process Station Group
 | 
			
		||||
			if g:
 | 
			
		||||
				self.g[tabFilename] = g
 | 
			
		||||
 | 
			
		||||
		except Exception as e:
 | 
			
		||||
			raise e
 | 
			
		||||
 | 
			
		||||
		finally:
 | 
			
		||||
			if fd:
 | 
			
		||||
				fd.close()
 | 
			
		||||
	
 | 
			
		||||
	def check(self):
 | 
			
		||||
		# Instrument alone check
 | 
			
		||||
		if self.i.keys:
 | 
			
		||||
			print("\nCheking Instruments Loaded:\n", file=sys.stderr)
 | 
			
		||||
			error = self.i.check(self.n)
 | 
			
		||||
			if error:
 | 
			
		||||
				for e in error: print(e, file=sys.stderr)
 | 
			
		||||
		else:
 | 
			
		||||
			print("\nNo instruments loaded", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
		# Cross Check
 | 
			
		||||
		error = []
 | 
			
		||||
		if self.n:
 | 
			
		||||
			print("\nChecking Networks Loaded:\n", file=sys.stderr)
 | 
			
		||||
			for network in self.n.values():
 | 
			
		||||
				error.extend(network.check(self.i))
 | 
			
		||||
			if error:
 | 
			
		||||
				for e in error: print(e, file=sys.stderr)
 | 
			
		||||
		else:
 | 
			
		||||
			print("\nNo network/stations loaded.", file=sys.stderr)
 | 
			
		||||
	
 | 
			
		||||
	def sc3Obj(self, sc3i = None):
 | 
			
		||||
		if not sc3i:
 | 
			
		||||
			sc3i = seiscomp.datamodel.Inventory()
 | 
			
		||||
			
 | 
			
		||||
		for network in list(self.n.values()):
 | 
			
		||||
			sc3n = network.sc3Obj(self.i)
 | 
			
		||||
			sc3i.add(sc3n)
 | 
			
		||||
		
 | 
			
		||||
		for sc3o in self.i.sc3Objs():
 | 
			
		||||
			sc3i.add(sc3o)
 | 
			
		||||
		
 | 
			
		||||
		self.stationResolver.collectStations(sc3i)
 | 
			
		||||
 | 
			
		||||
		for stationGroup in list(self.g.values()):
 | 
			
		||||
			sc3g = stationGroup.sc3Obj(self.stationResolver)
 | 
			
		||||
			sc3i.add(sc3g)
 | 
			
		||||
		
 | 
			
		||||
		return sc3i
 | 
			
		||||
							
								
								
									
										31
									
								
								lib/python/nettab/test/filters/q330_b100_1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								lib/python/nettab/test/filters/q330_b100_1
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
  0  1.219929e-16  0.000000e+00
 | 
			
		||||
  1  3.161921e-10  0.000000e+00
 | 
			
		||||
  2 -4.314652e-08  0.000000e+00
 | 
			
		||||
  3 -5.635558e-07  0.000000e+00
 | 
			
		||||
  4 -1.267008e-04  0.000000e+00
 | 
			
		||||
  5  3.658144e-03  0.000000e+00
 | 
			
		||||
  6  1.675314e-04  0.000000e+00
 | 
			
		||||
  7 -5.404505e-03  0.000000e+00
 | 
			
		||||
  8  1.278609e-02  0.000000e+00
 | 
			
		||||
  9 -1.803566e-02  0.000000e+00
 | 
			
		||||
 10  1.473116e-02  0.000000e+00
 | 
			
		||||
 11  3.226941e-03  0.000000e+00
 | 
			
		||||
 12 -3.859694e-02  0.000000e+00
 | 
			
		||||
 13  8.883527e-02  0.000000e+00
 | 
			
		||||
 14 -1.482427e-01  0.000000e+00
 | 
			
		||||
 15  2.177661e-01  0.000000e+00
 | 
			
		||||
 16  8.099144e-01  0.000000e+00
 | 
			
		||||
 17  1.245959e-01  0.000000e+00
 | 
			
		||||
 18 -1.230407e-01  0.000000e+00
 | 
			
		||||
 19  8.899753e-02  0.000000e+00
 | 
			
		||||
 20 -4.850157e-02  0.000000e+00
 | 
			
		||||
 21  1.425912e-02  0.000000e+00
 | 
			
		||||
 22  6.896391e-03  0.000000e+00
 | 
			
		||||
 23 -1.444342e-02  0.000000e+00
 | 
			
		||||
 24  1.242861e-02  0.000000e+00
 | 
			
		||||
 25 -6.568726e-03  0.000000e+00
 | 
			
		||||
 26  1.522040e-03  0.000000e+00
 | 
			
		||||
 27  3.142093e-03  0.000000e+00
 | 
			
		||||
 28  3.656274e-05  0.000000e+00
 | 
			
		||||
 29 -2.152995e-06  0.000000e+00
 | 
			
		||||
 30 -2.597827e-07  0.000000e+00
 | 
			
		||||
							
								
								
									
										65
									
								
								lib/python/nettab/test/filters/q330_b100_100
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								lib/python/nettab/test/filters/q330_b100_100
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,65 @@
 | 
			
		||||
  0  1.315493e-11  0.000000e+00
 | 
			
		||||
  1  1.501065e-04  0.000000e+00
 | 
			
		||||
  2  1.339681e-02  0.000000e+00
 | 
			
		||||
  3  1.644292e-01  0.000000e+00
 | 
			
		||||
  4  5.688094e-01  0.000000e+00
 | 
			
		||||
  5  5.173835e-01  0.000000e+00
 | 
			
		||||
  6 -2.608360e-01  0.000000e+00
 | 
			
		||||
  7 -1.220329e-01  0.000000e+00
 | 
			
		||||
  8  2.571813e-01  0.000000e+00
 | 
			
		||||
  9 -2.029026e-01  0.000000e+00
 | 
			
		||||
 10  7.075881e-02  0.000000e+00
 | 
			
		||||
 11  3.879666e-02  0.000000e+00
 | 
			
		||||
 12 -1.143135e-01  0.000000e+00
 | 
			
		||||
 13  1.354797e-01  0.000000e+00
 | 
			
		||||
 14 -1.114475e-01  0.000000e+00
 | 
			
		||||
 15  6.705481e-02  0.000000e+00
 | 
			
		||||
 16 -1.927124e-02  0.000000e+00
 | 
			
		||||
 17 -2.093129e-02  0.000000e+00
 | 
			
		||||
 18  4.768056e-02  0.000000e+00
 | 
			
		||||
 19 -5.933829e-02  0.000000e+00
 | 
			
		||||
 20  5.757931e-02  0.000000e+00
 | 
			
		||||
 21 -4.623331e-02  0.000000e+00
 | 
			
		||||
 22  2.977715e-02  0.000000e+00
 | 
			
		||||
 23 -1.248294e-02  0.000000e+00
 | 
			
		||||
 24 -2.366075e-03  0.000000e+00
 | 
			
		||||
 25  1.278821e-02  0.000000e+00
 | 
			
		||||
 26 -1.846982e-02  0.000000e+00
 | 
			
		||||
 27  1.879725e-02  0.000000e+00
 | 
			
		||||
 28 -1.713865e-02  0.000000e+00
 | 
			
		||||
 29  1.278199e-02  0.000000e+00
 | 
			
		||||
 30 -7.675787e-03  0.000000e+00
 | 
			
		||||
 31  3.255159e-03  0.000000e+00
 | 
			
		||||
 32 -8.947563e-05  0.000000e+00
 | 
			
		||||
 33 -1.778758e-03  0.000000e+00
 | 
			
		||||
 34  2.596043e-03  0.000000e+00
 | 
			
		||||
 35 -2.666169e-03  0.000000e+00
 | 
			
		||||
 36  2.307403e-03  0.000000e+00
 | 
			
		||||
 37 -1.770516e-03  0.000000e+00
 | 
			
		||||
 38  1.218643e-03  0.000000e+00
 | 
			
		||||
 39 -7.460492e-04  0.000000e+00
 | 
			
		||||
 40  3.921752e-04  0.000000e+00
 | 
			
		||||
 41 -1.583665e-04  0.000000e+00
 | 
			
		||||
 42  2.437801e-05  0.000000e+00
 | 
			
		||||
 43  3.807573e-05  0.000000e+00
 | 
			
		||||
 44 -5.618048e-05  0.000000e+00
 | 
			
		||||
 45  5.152771e-05  0.000000e+00
 | 
			
		||||
 46 -3.856469e-05  0.000000e+00
 | 
			
		||||
 47  2.530286e-05  0.000000e+00
 | 
			
		||||
 48 -1.512465e-05  0.000000e+00
 | 
			
		||||
 49  8.739795e-06  0.000000e+00
 | 
			
		||||
 50 -4.648117e-06  0.000000e+00
 | 
			
		||||
 51  1.376276e-06  0.000000e+00
 | 
			
		||||
 52  7.042064e-07  0.000000e+00
 | 
			
		||||
 53  2.241873e-07  0.000000e+00
 | 
			
		||||
 54 -1.251026e-06  0.000000e+00
 | 
			
		||||
 55  1.066771e-07  0.000000e+00
 | 
			
		||||
 56  2.642876e-07  0.000000e+00
 | 
			
		||||
 57  3.226638e-07  0.000000e+00
 | 
			
		||||
 58 -8.074162e-08  0.000000e+00
 | 
			
		||||
 59 -1.099048e-07  0.000000e+00
 | 
			
		||||
 60 -3.325203e-08  0.000000e+00
 | 
			
		||||
 61  1.388506e-08  0.000000e+00
 | 
			
		||||
 62  1.056275e-08  0.000000e+00
 | 
			
		||||
 63  2.577911e-09  0.000000e+00
 | 
			
		||||
 64 -7.018623e-10  0.000000e+00
 | 
			
		||||
							
								
								
									
										67
									
								
								lib/python/nettab/test/filters/q330_b100_20
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								lib/python/nettab/test/filters/q330_b100_20
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,67 @@
 | 
			
		||||
  0 -3.653417e-17  0.000000e+00
 | 
			
		||||
  1  3.674881e-08  0.000000e+00
 | 
			
		||||
  2 -4.270596e-07  0.000000e+00
 | 
			
		||||
  3  1.145020e-06  0.000000e+00
 | 
			
		||||
  4 -1.875941e-07  0.000000e+00
 | 
			
		||||
  5 -3.372737e-07  0.000000e+00
 | 
			
		||||
  6  2.787469e-06  0.000000e+00
 | 
			
		||||
  7 -3.744026e-06  0.000000e+00
 | 
			
		||||
  8  5.411719e-06  0.000000e+00
 | 
			
		||||
  9  7.473363e-06  0.000000e+00
 | 
			
		||||
 10 -5.177595e-04  0.000000e+00
 | 
			
		||||
 11  2.106768e-04  0.000000e+00
 | 
			
		||||
 12  4.632577e-05  0.000000e+00
 | 
			
		||||
 13 -6.082222e-04  0.000000e+00
 | 
			
		||||
 14  1.441747e-03  0.000000e+00
 | 
			
		||||
 15 -2.406265e-03  0.000000e+00
 | 
			
		||||
 16  3.225338e-03  0.000000e+00
 | 
			
		||||
 17 -3.506390e-03  0.000000e+00
 | 
			
		||||
 18  2.814411e-03  0.000000e+00
 | 
			
		||||
 19 -7.719714e-04  0.000000e+00
 | 
			
		||||
 20 -2.805119e-03  0.000000e+00
 | 
			
		||||
 21  7.778055e-03  0.000000e+00
 | 
			
		||||
 22 -1.358146e-02  0.000000e+00
 | 
			
		||||
 23  1.917646e-02  0.000000e+00
 | 
			
		||||
 24 -2.297035e-02  0.000000e+00
 | 
			
		||||
 25  2.403979e-02  0.000000e+00
 | 
			
		||||
 26 -2.209865e-02  0.000000e+00
 | 
			
		||||
 27  8.607339e-03  0.000000e+00
 | 
			
		||||
 28  1.175252e-02  0.000000e+00
 | 
			
		||||
 29 -4.477868e-02  0.000000e+00
 | 
			
		||||
 30  9.649231e-02  0.000000e+00
 | 
			
		||||
 31 -1.917548e-01  0.000000e+00
 | 
			
		||||
 32  5.276523e-01  0.000000e+00
 | 
			
		||||
 33  7.241670e-01  0.000000e+00
 | 
			
		||||
 34 -1.569053e-01  0.000000e+00
 | 
			
		||||
 35  4.425742e-02  0.000000e+00
 | 
			
		||||
 36  3.141684e-03  0.000000e+00
 | 
			
		||||
 37 -2.667144e-02  0.000000e+00
 | 
			
		||||
 38  3.615316e-02  0.000000e+00
 | 
			
		||||
 39 -3.856867e-02  0.000000e+00
 | 
			
		||||
 40  3.108417e-02  0.000000e+00
 | 
			
		||||
 41 -2.352589e-02  0.000000e+00
 | 
			
		||||
 42  1.532109e-02  0.000000e+00
 | 
			
		||||
 43 -7.403983e-03  0.000000e+00
 | 
			
		||||
 44  1.096454e-03  0.000000e+00
 | 
			
		||||
 45  3.097965e-03  0.000000e+00
 | 
			
		||||
 46 -5.193199e-03  0.000000e+00
 | 
			
		||||
 47  5.561311e-03  0.000000e+00
 | 
			
		||||
 48 -4.761101e-03  0.000000e+00
 | 
			
		||||
 49  3.382132e-03  0.000000e+00
 | 
			
		||||
 50 -1.920520e-03  0.000000e+00
 | 
			
		||||
 51  7.152175e-04  0.000000e+00
 | 
			
		||||
 52  7.677194e-05  0.000000e+00
 | 
			
		||||
 53 -4.518973e-04  0.000000e+00
 | 
			
		||||
 54  5.026997e-04  0.000000e+00
 | 
			
		||||
 55 -5.650370e-04  0.000000e+00
 | 
			
		||||
 56 -5.568005e-05  0.000000e+00
 | 
			
		||||
 57  1.577356e-05  0.000000e+00
 | 
			
		||||
 58 -1.419847e-06  0.000000e+00
 | 
			
		||||
 59  8.149094e-07  0.000000e+00
 | 
			
		||||
 60  6.807946e-07  0.000000e+00
 | 
			
		||||
 61 -1.252728e-06  0.000000e+00
 | 
			
		||||
 62  1.524350e-06  0.000000e+00
 | 
			
		||||
 63 -2.833359e-07  0.000000e+00
 | 
			
		||||
 64 -1.063838e-08  0.000000e+00
 | 
			
		||||
 65  1.257120e-09  0.000000e+00
 | 
			
		||||
 66 -5.429542e-11  0.000000e+00
 | 
			
		||||
							
								
								
									
										39
									
								
								lib/python/nettab/test/filters/q330_b100_40
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								lib/python/nettab/test/filters/q330_b100_40
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
  0  4.189518e-13  0.000000e+00
 | 
			
		||||
  1  3.303176e-04  0.000000e+00
 | 
			
		||||
  2  1.029213e-03  0.000000e+00
 | 
			
		||||
  3 -3.141228e-03  0.000000e+00
 | 
			
		||||
  4  2.057093e-04  0.000000e+00
 | 
			
		||||
  5  1.525213e-03  0.000000e+00
 | 
			
		||||
  6 -6.231927e-03  0.000000e+00
 | 
			
		||||
  7  1.048013e-02  0.000000e+00
 | 
			
		||||
  8 -1.312025e-02  0.000000e+00
 | 
			
		||||
  9  1.078214e-02  0.000000e+00
 | 
			
		||||
 10 -1.444550e-03  0.000000e+00
 | 
			
		||||
 11 -1.587295e-02  0.000000e+00
 | 
			
		||||
 12  3.950740e-02  0.000000e+00
 | 
			
		||||
 13 -6.510363e-02  0.000000e+00
 | 
			
		||||
 14  8.537156e-02  0.000000e+00
 | 
			
		||||
 15 -8.919134e-02  0.000000e+00
 | 
			
		||||
 16  5.006189e-02  0.000000e+00
 | 
			
		||||
 17  8.372328e-01  0.000000e+00
 | 
			
		||||
 18  2.667231e-01  0.000000e+00
 | 
			
		||||
 19 -1.666931e-01  0.000000e+00
 | 
			
		||||
 20  9.528399e-02  0.000000e+00
 | 
			
		||||
 21 -5.092177e-02  0.000000e+00
 | 
			
		||||
 22  1.614584e-02  0.000000e+00
 | 
			
		||||
 23  7.063624e-03  0.000000e+00
 | 
			
		||||
 24 -1.838771e-02  0.000000e+00
 | 
			
		||||
 25  1.994141e-02  0.000000e+00
 | 
			
		||||
 26 -1.548951e-02  0.000000e+00
 | 
			
		||||
 27  8.527354e-03  0.000000e+00
 | 
			
		||||
 28 -2.557887e-03  0.000000e+00
 | 
			
		||||
 29 -1.811026e-03  0.000000e+00
 | 
			
		||||
 30  2.426493e-03  0.000000e+00
 | 
			
		||||
 31 -3.757695e-03  0.000000e+00
 | 
			
		||||
 32  4.672927e-04  0.000000e+00
 | 
			
		||||
 33  6.330721e-04  0.000000e+00
 | 
			
		||||
 34 -1.568741e-06  0.000000e+00
 | 
			
		||||
 35 -1.254798e-05  0.000000e+00
 | 
			
		||||
 36  3.210405e-07  0.000000e+00
 | 
			
		||||
 37 -2.633241e-08  0.000000e+00
 | 
			
		||||
 38 -5.099975e-08  0.000000e+00
 | 
			
		||||
							
								
								
									
										81
									
								
								lib/python/nettab/test/filters/q330_b100_50
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								lib/python/nettab/test/filters/q330_b100_50
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
			
		||||
  0  6.915055e-16  0.000000e+00
 | 
			
		||||
  1  9.981469e-07  0.000000e+00
 | 
			
		||||
  2  8.986285e-05  0.000000e+00
 | 
			
		||||
  3  3.536859e-04  0.000000e+00
 | 
			
		||||
  4 -3.196747e-04  0.000000e+00
 | 
			
		||||
  5  2.398310e-04  0.000000e+00
 | 
			
		||||
  6  4.343304e-05  0.000000e+00
 | 
			
		||||
  7 -6.140379e-04  0.000000e+00
 | 
			
		||||
  8  1.450240e-03  0.000000e+00
 | 
			
		||||
  9 -2.414179e-03  0.000000e+00
 | 
			
		||||
 10  3.243791e-03  0.000000e+00
 | 
			
		||||
 11 -3.565280e-03  0.000000e+00
 | 
			
		||||
 12  2.956281e-03  0.000000e+00
 | 
			
		||||
 13 -1.048729e-03  0.000000e+00
 | 
			
		||||
 14 -2.353488e-03  0.000000e+00
 | 
			
		||||
 15  7.146584e-03  0.000000e+00
 | 
			
		||||
 16 -1.283558e-02  0.000000e+00
 | 
			
		||||
 17  1.849560e-02  0.000000e+00
 | 
			
		||||
 18 -2.280356e-02  0.000000e+00
 | 
			
		||||
 19  2.414348e-02  0.000000e+00
 | 
			
		||||
 20 -2.075420e-02  0.000000e+00
 | 
			
		||||
 21  1.085375e-02  0.000000e+00
 | 
			
		||||
 22  7.376841e-03  0.000000e+00
 | 
			
		||||
 23 -3.628054e-02  0.000000e+00
 | 
			
		||||
 24  8.073029e-02  0.000000e+00
 | 
			
		||||
 25 -1.563791e-01  0.000000e+00
 | 
			
		||||
 26  5.966318e-01  0.000000e+00
 | 
			
		||||
 27  6.616155e-01  0.000000e+00
 | 
			
		||||
 28 -1.985033e-01  0.000000e+00
 | 
			
		||||
 29  5.962802e-02  0.000000e+00
 | 
			
		||||
 30 -1.201563e-02  0.000000e+00
 | 
			
		||||
 31 -2.031269e-02  0.000000e+00
 | 
			
		||||
 32  3.489734e-02  0.000000e+00
 | 
			
		||||
 33 -3.783039e-02  0.000000e+00
 | 
			
		||||
 34  3.414802e-02  0.000000e+00
 | 
			
		||||
 35 -2.681871e-02  0.000000e+00
 | 
			
		||||
 36  1.805448e-02  0.000000e+00
 | 
			
		||||
 37 -9.684112e-03  0.000000e+00
 | 
			
		||||
 38  1.924548e-03  0.000000e+00
 | 
			
		||||
 39  2.270220e-03  0.000000e+00
 | 
			
		||||
 40 -4.929948e-03  0.000000e+00
 | 
			
		||||
 41  5.783542e-03  0.000000e+00
 | 
			
		||||
 42 -5.278113e-03  0.000000e+00
 | 
			
		||||
 43  4.012361e-03  0.000000e+00
 | 
			
		||||
 44 -2.512171e-03  0.000000e+00
 | 
			
		||||
 45  1.166119e-03  0.000000e+00
 | 
			
		||||
 46 -1.915292e-04  0.000000e+00
 | 
			
		||||
 47 -3.549948e-04  0.000000e+00
 | 
			
		||||
 48  5.355819e-04  0.000000e+00
 | 
			
		||||
 49 -4.810171e-04  0.000000e+00
 | 
			
		||||
 50  4.186318e-04  0.000000e+00
 | 
			
		||||
 51  7.809605e-05  0.000000e+00
 | 
			
		||||
 52 -5.470072e-06  0.000000e+00
 | 
			
		||||
 53 -2.123757e-06  0.000000e+00
 | 
			
		||||
 54 -6.620526e-07  0.000000e+00
 | 
			
		||||
 55  7.238966e-07  0.000000e+00
 | 
			
		||||
 56  1.013226e-06  0.000000e+00
 | 
			
		||||
 57 -1.929203e-06  0.000000e+00
 | 
			
		||||
 58  7.801228e-07  0.000000e+00
 | 
			
		||||
 59 -7.887565e-07  0.000000e+00
 | 
			
		||||
 60  5.818626e-07  0.000000e+00
 | 
			
		||||
 61  3.221050e-08  0.000000e+00
 | 
			
		||||
 62 -1.076378e-07  0.000000e+00
 | 
			
		||||
 63  1.999555e-08  0.000000e+00
 | 
			
		||||
 64 -7.052141e-08  0.000000e+00
 | 
			
		||||
 65 -1.357645e-08  0.000000e+00
 | 
			
		||||
 66 -3.311185e-08  0.000000e+00
 | 
			
		||||
 67  1.552117e-08  0.000000e+00
 | 
			
		||||
 68 -5.395556e-09  0.000000e+00
 | 
			
		||||
 69  7.791274e-09  0.000000e+00
 | 
			
		||||
 70  2.075919e-10  0.000000e+00
 | 
			
		||||
 71 -9.326780e-10  0.000000e+00
 | 
			
		||||
 72  1.850689e-09  0.000000e+00
 | 
			
		||||
 73 -1.973863e-09  0.000000e+00
 | 
			
		||||
 74  1.334281e-09  0.000000e+00
 | 
			
		||||
 75 -6.315467e-10  0.000000e+00
 | 
			
		||||
 76  6.994718e-11  0.000000e+00
 | 
			
		||||
 77  1.148694e-10  0.000000e+00
 | 
			
		||||
 78 -5.595614e-11  0.000000e+00
 | 
			
		||||
 79  5.760568e-12  0.000000e+00
 | 
			
		||||
 80 -5.489862e-12  0.000000e+00
 | 
			
		||||
							
								
								
									
										400
									
								
								lib/python/nettab/test/filters/scp_deci10.1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										400
									
								
								lib/python/nettab/test/filters/scp_deci10.1
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,400 @@
 | 
			
		||||
  0 -1.280410E-09  0.000000E+00
 | 
			
		||||
  1  9.089140E-09  0.000000E+00
 | 
			
		||||
  2  2.857200E-08  0.000000E+00
 | 
			
		||||
  3  7.068940E-08  0.000000E+00
 | 
			
		||||
  4  1.503850E-07  0.000000E+00
 | 
			
		||||
  5  2.898420E-07  0.000000E+00
 | 
			
		||||
  6  5.199920E-07  0.000000E+00
 | 
			
		||||
  7  8.824160E-07  0.000000E+00
 | 
			
		||||
  8  1.431250E-06  0.000000E+00
 | 
			
		||||
  9  2.234920E-06  0.000000E+00
 | 
			
		||||
 10  3.377490E-06  0.000000E+00
 | 
			
		||||
 11  4.959500E-06  0.000000E+00
 | 
			
		||||
 12  7.097790E-06  0.000000E+00
 | 
			
		||||
 13  9.924440E-06  0.000000E+00
 | 
			
		||||
 14  1.358420E-05  0.000000E+00
 | 
			
		||||
 15  1.823040E-05  0.000000E+00
 | 
			
		||||
 16  2.401920E-05  0.000000E+00
 | 
			
		||||
 17  3.110180E-05  0.000000E+00
 | 
			
		||||
 18  3.961540E-05  0.000000E+00
 | 
			
		||||
 19  4.967160E-05  0.000000E+00
 | 
			
		||||
 20  6.134480E-05  0.000000E+00
 | 
			
		||||
 21  7.465790E-05  0.000000E+00
 | 
			
		||||
 22  8.956970E-05  0.000000E+00
 | 
			
		||||
 23  1.059620E-04  0.000000E+00
 | 
			
		||||
 24  1.236260E-04  0.000000E+00
 | 
			
		||||
 25  1.422580E-04  0.000000E+00
 | 
			
		||||
 26  1.614470E-04  0.000000E+00
 | 
			
		||||
 27  1.806800E-04  0.000000E+00
 | 
			
		||||
 28  1.993440E-04  0.000000E+00
 | 
			
		||||
 29  2.167350E-04  0.000000E+00
 | 
			
		||||
 30  2.320800E-04  0.000000E+00
 | 
			
		||||
 31  2.445590E-04  0.000000E+00
 | 
			
		||||
 32  2.533370E-04  0.000000E+00
 | 
			
		||||
 33  2.576020E-04  0.000000E+00
 | 
			
		||||
 34  2.566110E-04  0.000000E+00
 | 
			
		||||
 35  2.497330E-04  0.000000E+00
 | 
			
		||||
 36  2.364990E-04  0.000000E+00
 | 
			
		||||
 37  2.166500E-04  0.000000E+00
 | 
			
		||||
 38  1.901760E-04  0.000000E+00
 | 
			
		||||
 39  1.573550E-04  0.000000E+00
 | 
			
		||||
 40  1.187790E-04  0.000000E+00
 | 
			
		||||
 41  7.536150E-05  0.000000E+00
 | 
			
		||||
 42  2.833800E-05  0.000000E+00
 | 
			
		||||
 43 -2.075750E-05  0.000000E+00
 | 
			
		||||
 44 -7.013260E-05  0.000000E+00
 | 
			
		||||
 45 -1.177970E-04  0.000000E+00
 | 
			
		||||
 46 -1.616380E-04  0.000000E+00
 | 
			
		||||
 47 -1.995190E-04  0.000000E+00
 | 
			
		||||
 48 -2.293810E-04  0.000000E+00
 | 
			
		||||
 49 -2.493630E-04  0.000000E+00
 | 
			
		||||
 50 -2.579120E-04  0.000000E+00
 | 
			
		||||
 51 -2.539050E-04  0.000000E+00
 | 
			
		||||
 52 -2.367430E-04  0.000000E+00
 | 
			
		||||
 53 -2.064400E-04  0.000000E+00
 | 
			
		||||
 54 -1.636770E-04  0.000000E+00
 | 
			
		||||
 55 -1.098340E-04  0.000000E+00
 | 
			
		||||
 56 -4.697750E-05  0.000000E+00
 | 
			
		||||
 57  2.218660E-05  0.000000E+00
 | 
			
		||||
 58  9.440430E-05  0.000000E+00
 | 
			
		||||
 59  1.660030E-04  0.000000E+00
 | 
			
		||||
 60  2.330560E-04  0.000000E+00
 | 
			
		||||
 61  2.915810E-04  0.000000E+00
 | 
			
		||||
 62  3.377580E-04  0.000000E+00
 | 
			
		||||
 63  3.681570E-04  0.000000E+00
 | 
			
		||||
 64  3.799620E-04  0.000000E+00
 | 
			
		||||
 65  3.711900E-04  0.000000E+00
 | 
			
		||||
 66  3.408650E-04  0.000000E+00
 | 
			
		||||
 67  2.891620E-04  0.000000E+00
 | 
			
		||||
 68  2.174900E-04  0.000000E+00
 | 
			
		||||
 69  1.285060E-04  0.000000E+00
 | 
			
		||||
 70  2.606830E-05  0.000000E+00
 | 
			
		||||
 71 -8.490010E-05  0.000000E+00
 | 
			
		||||
 72 -1.986100E-04  0.000000E+00
 | 
			
		||||
 73 -3.086790E-04  0.000000E+00
 | 
			
		||||
 74 -4.084630E-04  0.000000E+00
 | 
			
		||||
 75 -4.914240E-04  0.000000E+00
 | 
			
		||||
 76 -5.515290E-04  0.000000E+00
 | 
			
		||||
 77 -5.836450E-04  0.000000E+00
 | 
			
		||||
 78 -5.839130E-04  0.000000E+00
 | 
			
		||||
 79 -5.500750E-04  0.000000E+00
 | 
			
		||||
 80 -4.817300E-04  0.000000E+00
 | 
			
		||||
 81 -3.804970E-04  0.000000E+00
 | 
			
		||||
 82 -2.500650E-04  0.000000E+00
 | 
			
		||||
 83 -9.613190E-05  0.000000E+00
 | 
			
		||||
 84  7.379770E-05  0.000000E+00
 | 
			
		||||
 85  2.507300E-04  0.000000E+00
 | 
			
		||||
 86  4.246150E-04  0.000000E+00
 | 
			
		||||
 87  5.848830E-04  0.000000E+00
 | 
			
		||||
 88  7.210410E-04  0.000000E+00
 | 
			
		||||
 89  8.233180E-04  0.000000E+00
 | 
			
		||||
 90  8.833110E-04  0.000000E+00
 | 
			
		||||
 91  8.945860E-04  0.000000E+00
 | 
			
		||||
 92  8.532140E-04  0.000000E+00
 | 
			
		||||
 93  7.581840E-04  0.000000E+00
 | 
			
		||||
 94  6.116610E-04  0.000000E+00
 | 
			
		||||
 95  4.190820E-04  0.000000E+00
 | 
			
		||||
 96  1.890410E-04  0.000000E+00
 | 
			
		||||
 97 -6.701870E-05  0.000000E+00
 | 
			
		||||
 98 -3.353110E-04  0.000000E+00
 | 
			
		||||
 99 -6.003940E-04  0.000000E+00
 | 
			
		||||
100 -8.460070E-04  0.000000E+00
 | 
			
		||||
101 -1.056010E-03  0.000000E+00
 | 
			
		||||
102 -1.215390E-03  0.000000E+00
 | 
			
		||||
103 -1.311250E-03  0.000000E+00
 | 
			
		||||
104 -1.333740E-03  0.000000E+00
 | 
			
		||||
105 -1.276860E-03  0.000000E+00
 | 
			
		||||
106 -1.139110E-03  0.000000E+00
 | 
			
		||||
107 -9.238090E-04  0.000000E+00
 | 
			
		||||
108 -6.392740E-04  0.000000E+00
 | 
			
		||||
109 -2.985730E-04  0.000000E+00
 | 
			
		||||
110  8.095210E-05  0.000000E+00
 | 
			
		||||
111  4.784920E-04  0.000000E+00
 | 
			
		||||
112  8.708350E-04  0.000000E+00
 | 
			
		||||
113  1.233650E-03  0.000000E+00
 | 
			
		||||
114  1.542910E-03  0.000000E+00
 | 
			
		||||
115  1.776410E-03  0.000000E+00
 | 
			
		||||
116  1.915250E-03  0.000000E+00
 | 
			
		||||
117  1.945200E-03  0.000000E+00
 | 
			
		||||
118  1.857870E-03  0.000000E+00
 | 
			
		||||
119  1.651590E-03  0.000000E+00
 | 
			
		||||
120  1.331930E-03  0.000000E+00
 | 
			
		||||
121  9.117790E-04  0.000000E+00
 | 
			
		||||
122  4.110140E-04  0.000000E+00
 | 
			
		||||
123 -1.443240E-04  0.000000E+00
 | 
			
		||||
124 -7.232630E-04  0.000000E+00
 | 
			
		||||
125 -1.291520E-03  0.000000E+00
 | 
			
		||||
126 -1.813440E-03  0.000000E+00
 | 
			
		||||
127 -2.254090E-03  0.000000E+00
 | 
			
		||||
128 -2.581490E-03  0.000000E+00
 | 
			
		||||
129 -2.768760E-03  0.000000E+00
 | 
			
		||||
130 -2.796120E-03  0.000000E+00
 | 
			
		||||
131 -2.652470E-03  0.000000E+00
 | 
			
		||||
132 -2.336640E-03  0.000000E+00
 | 
			
		||||
133 -1.858050E-03  0.000000E+00
 | 
			
		||||
134 -1.236750E-03  0.000000E+00
 | 
			
		||||
135 -5.027860E-04  0.000000E+00
 | 
			
		||||
136  3.050470E-04  0.000000E+00
 | 
			
		||||
137  1.141090E-03  0.000000E+00
 | 
			
		||||
138  1.955230E-03  0.000000E+00
 | 
			
		||||
139  2.695760E-03  0.000000E+00
 | 
			
		||||
140  3.312460E-03  0.000000E+00
 | 
			
		||||
141  3.759760E-03  0.000000E+00
 | 
			
		||||
142  3.999910E-03  0.000000E+00
 | 
			
		||||
143  4.005660E-03  0.000000E+00
 | 
			
		||||
144  3.762670E-03  0.000000E+00
 | 
			
		||||
145  3.271090E-03  0.000000E+00
 | 
			
		||||
146  2.546440E-03  0.000000E+00
 | 
			
		||||
147  1.619580E-03  0.000000E+00
 | 
			
		||||
148  5.357070E-04  0.000000E+00
 | 
			
		||||
149 -6.475150E-04  0.000000E+00
 | 
			
		||||
150 -1.862780E-03  0.000000E+00
 | 
			
		||||
151 -3.036670E-03  0.000000E+00
 | 
			
		||||
152 -4.093770E-03  0.000000E+00
 | 
			
		||||
153 -4.961150E-03  0.000000E+00
 | 
			
		||||
154 -5.573010E-03  0.000000E+00
 | 
			
		||||
155 -5.875080E-03  0.000000E+00
 | 
			
		||||
156 -5.828670E-03  0.000000E+00
 | 
			
		||||
157 -5.414010E-03  0.000000E+00
 | 
			
		||||
158 -4.632620E-03  0.000000E+00
 | 
			
		||||
159 -3.508570E-03  0.000000E+00
 | 
			
		||||
160 -2.088510E-03  0.000000E+00
 | 
			
		||||
161 -4.402630E-04  0.000000E+00
 | 
			
		||||
162  1.349800E-03  0.000000E+00
 | 
			
		||||
163  3.180770E-03  0.000000E+00
 | 
			
		||||
164  4.942220E-03  0.000000E+00
 | 
			
		||||
165  6.520130E-03  0.000000E+00
 | 
			
		||||
166  7.803440E-03  0.000000E+00
 | 
			
		||||
167  8.690760E-03  0.000000E+00
 | 
			
		||||
168  9.097010E-03  0.000000E+00
 | 
			
		||||
169  8.959570E-03  0.000000E+00
 | 
			
		||||
170  8.243470E-03  0.000000E+00
 | 
			
		||||
171  6.945480E-03  0.000000E+00
 | 
			
		||||
172  5.096570E-03  0.000000E+00
 | 
			
		||||
173  2.762750E-03  0.000000E+00
 | 
			
		||||
174  4.398920E-05  0.000000E+00
 | 
			
		||||
175 -2.928690E-03  0.000000E+00
 | 
			
		||||
176 -5.998030E-03  0.000000E+00
 | 
			
		||||
177 -8.986910E-03  0.000000E+00
 | 
			
		||||
178 -1.170620E-02  0.000000E+00
 | 
			
		||||
179 -1.396360E-02  0.000000E+00
 | 
			
		||||
180 -1.557300E-02  0.000000E+00
 | 
			
		||||
181 -1.636440E-02  0.000000E+00
 | 
			
		||||
182 -1.619300E-02  0.000000E+00
 | 
			
		||||
183 -1.494760E-02  0.000000E+00
 | 
			
		||||
184 -1.255800E-02  0.000000E+00
 | 
			
		||||
185 -9.000540E-03  0.000000E+00
 | 
			
		||||
186 -4.301130E-03  0.000000E+00
 | 
			
		||||
187  1.463060E-03  0.000000E+00
 | 
			
		||||
188  8.165080E-03  0.000000E+00
 | 
			
		||||
189  1.563180E-02  0.000000E+00
 | 
			
		||||
190  2.364960E-02  0.000000E+00
 | 
			
		||||
191  3.197290E-02  0.000000E+00
 | 
			
		||||
192  4.033310E-02  0.000000E+00
 | 
			
		||||
193  4.845020E-02  0.000000E+00
 | 
			
		||||
194  5.604420E-02  0.000000E+00
 | 
			
		||||
195  6.284710E-02  0.000000E+00
 | 
			
		||||
196  6.861480E-02  0.000000E+00
 | 
			
		||||
197  7.313740E-02  0.000000E+00
 | 
			
		||||
198  7.624880E-02  0.000000E+00
 | 
			
		||||
199  7.783390E-02  0.000000E+00
 | 
			
		||||
200  7.783390E-02  0.000000E+00
 | 
			
		||||
201  7.624880E-02  0.000000E+00
 | 
			
		||||
202  7.313740E-02  0.000000E+00
 | 
			
		||||
203  6.861480E-02  0.000000E+00
 | 
			
		||||
204  6.284710E-02  0.000000E+00
 | 
			
		||||
205  5.604420E-02  0.000000E+00
 | 
			
		||||
206  4.845020E-02  0.000000E+00
 | 
			
		||||
207  4.033310E-02  0.000000E+00
 | 
			
		||||
208  3.197290E-02  0.000000E+00
 | 
			
		||||
209  2.364960E-02  0.000000E+00
 | 
			
		||||
210  1.563180E-02  0.000000E+00
 | 
			
		||||
211  8.165080E-03  0.000000E+00
 | 
			
		||||
212  1.463060E-03  0.000000E+00
 | 
			
		||||
213 -4.301130E-03  0.000000E+00
 | 
			
		||||
214 -9.000540E-03  0.000000E+00
 | 
			
		||||
215 -1.255800E-02  0.000000E+00
 | 
			
		||||
216 -1.494760E-02  0.000000E+00
 | 
			
		||||
217 -1.619300E-02  0.000000E+00
 | 
			
		||||
218 -1.636440E-02  0.000000E+00
 | 
			
		||||
219 -1.557300E-02  0.000000E+00
 | 
			
		||||
220 -1.396360E-02  0.000000E+00
 | 
			
		||||
221 -1.170620E-02  0.000000E+00
 | 
			
		||||
222 -8.986910E-03  0.000000E+00
 | 
			
		||||
223 -5.998030E-03  0.000000E+00
 | 
			
		||||
224 -2.928690E-03  0.000000E+00
 | 
			
		||||
225  4.398920E-05  0.000000E+00
 | 
			
		||||
226  2.762750E-03  0.000000E+00
 | 
			
		||||
227  5.096570E-03  0.000000E+00
 | 
			
		||||
228  6.945480E-03  0.000000E+00
 | 
			
		||||
229  8.243470E-03  0.000000E+00
 | 
			
		||||
230  8.959570E-03  0.000000E+00
 | 
			
		||||
231  9.097010E-03  0.000000E+00
 | 
			
		||||
232  8.690760E-03  0.000000E+00
 | 
			
		||||
233  7.803440E-03  0.000000E+00
 | 
			
		||||
234  6.520130E-03  0.000000E+00
 | 
			
		||||
235  4.942220E-03  0.000000E+00
 | 
			
		||||
236  3.180770E-03  0.000000E+00
 | 
			
		||||
237  1.349800E-03  0.000000E+00
 | 
			
		||||
238 -4.402630E-04  0.000000E+00
 | 
			
		||||
239 -2.088510E-03  0.000000E+00
 | 
			
		||||
240 -3.508570E-03  0.000000E+00
 | 
			
		||||
241 -4.632620E-03  0.000000E+00
 | 
			
		||||
242 -5.414010E-03  0.000000E+00
 | 
			
		||||
243 -5.828670E-03  0.000000E+00
 | 
			
		||||
244 -5.875080E-03  0.000000E+00
 | 
			
		||||
245 -5.573010E-03  0.000000E+00
 | 
			
		||||
246 -4.961150E-03  0.000000E+00
 | 
			
		||||
247 -4.093770E-03  0.000000E+00
 | 
			
		||||
248 -3.036670E-03  0.000000E+00
 | 
			
		||||
249 -1.862780E-03  0.000000E+00
 | 
			
		||||
250 -6.475150E-04  0.000000E+00
 | 
			
		||||
251  5.357070E-04  0.000000E+00
 | 
			
		||||
252  1.619580E-03  0.000000E+00
 | 
			
		||||
253  2.546440E-03  0.000000E+00
 | 
			
		||||
254  3.271090E-03  0.000000E+00
 | 
			
		||||
255  3.762670E-03  0.000000E+00
 | 
			
		||||
256  4.005660E-03  0.000000E+00
 | 
			
		||||
257  3.999910E-03  0.000000E+00
 | 
			
		||||
258  3.759760E-03  0.000000E+00
 | 
			
		||||
259  3.312460E-03  0.000000E+00
 | 
			
		||||
260  2.695760E-03  0.000000E+00
 | 
			
		||||
261  1.955230E-03  0.000000E+00
 | 
			
		||||
262  1.141090E-03  0.000000E+00
 | 
			
		||||
263  3.050470E-04  0.000000E+00
 | 
			
		||||
264 -5.027860E-04  0.000000E+00
 | 
			
		||||
265 -1.236750E-03  0.000000E+00
 | 
			
		||||
266 -1.858050E-03  0.000000E+00
 | 
			
		||||
267 -2.336640E-03  0.000000E+00
 | 
			
		||||
268 -2.652470E-03  0.000000E+00
 | 
			
		||||
269 -2.796120E-03  0.000000E+00
 | 
			
		||||
270 -2.768760E-03  0.000000E+00
 | 
			
		||||
271 -2.581490E-03  0.000000E+00
 | 
			
		||||
272 -2.254090E-03  0.000000E+00
 | 
			
		||||
273 -1.813440E-03  0.000000E+00
 | 
			
		||||
274 -1.291520E-03  0.000000E+00
 | 
			
		||||
275 -7.232630E-04  0.000000E+00
 | 
			
		||||
276 -1.443240E-04  0.000000E+00
 | 
			
		||||
277  4.110140E-04  0.000000E+00
 | 
			
		||||
278  9.117790E-04  0.000000E+00
 | 
			
		||||
279  1.331930E-03  0.000000E+00
 | 
			
		||||
280  1.651590E-03  0.000000E+00
 | 
			
		||||
281  1.857870E-03  0.000000E+00
 | 
			
		||||
282  1.945200E-03  0.000000E+00
 | 
			
		||||
283  1.915250E-03  0.000000E+00
 | 
			
		||||
284  1.776410E-03  0.000000E+00
 | 
			
		||||
285  1.542910E-03  0.000000E+00
 | 
			
		||||
286  1.233650E-03  0.000000E+00
 | 
			
		||||
287  8.708350E-04  0.000000E+00
 | 
			
		||||
288  4.784920E-04  0.000000E+00
 | 
			
		||||
289  8.095210E-05  0.000000E+00
 | 
			
		||||
290 -2.985730E-04  0.000000E+00
 | 
			
		||||
291 -6.392740E-04  0.000000E+00
 | 
			
		||||
292 -9.238090E-04  0.000000E+00
 | 
			
		||||
293 -1.139110E-03  0.000000E+00
 | 
			
		||||
294 -1.276860E-03  0.000000E+00
 | 
			
		||||
295 -1.333740E-03  0.000000E+00
 | 
			
		||||
296 -1.311250E-03  0.000000E+00
 | 
			
		||||
297 -1.215390E-03  0.000000E+00
 | 
			
		||||
298 -1.056010E-03  0.000000E+00
 | 
			
		||||
299 -8.460070E-04  0.000000E+00
 | 
			
		||||
300 -6.003940E-04  0.000000E+00
 | 
			
		||||
301 -3.353110E-04  0.000000E+00
 | 
			
		||||
302 -6.701870E-05  0.000000E+00
 | 
			
		||||
303  1.890410E-04  0.000000E+00
 | 
			
		||||
304  4.190820E-04  0.000000E+00
 | 
			
		||||
305  6.116610E-04  0.000000E+00
 | 
			
		||||
306  7.581840E-04  0.000000E+00
 | 
			
		||||
307  8.532140E-04  0.000000E+00
 | 
			
		||||
308  8.945860E-04  0.000000E+00
 | 
			
		||||
309  8.833110E-04  0.000000E+00
 | 
			
		||||
310  8.233180E-04  0.000000E+00
 | 
			
		||||
311  7.210410E-04  0.000000E+00
 | 
			
		||||
312  5.848830E-04  0.000000E+00
 | 
			
		||||
313  4.246150E-04  0.000000E+00
 | 
			
		||||
314  2.507300E-04  0.000000E+00
 | 
			
		||||
315  7.379770E-05  0.000000E+00
 | 
			
		||||
316 -9.613190E-05  0.000000E+00
 | 
			
		||||
317 -2.500650E-04  0.000000E+00
 | 
			
		||||
318 -3.804970E-04  0.000000E+00
 | 
			
		||||
319 -4.817300E-04  0.000000E+00
 | 
			
		||||
320 -5.500750E-04  0.000000E+00
 | 
			
		||||
321 -5.839130E-04  0.000000E+00
 | 
			
		||||
322 -5.836450E-04  0.000000E+00
 | 
			
		||||
323 -5.515290E-04  0.000000E+00
 | 
			
		||||
324 -4.914240E-04  0.000000E+00
 | 
			
		||||
325 -4.084630E-04  0.000000E+00
 | 
			
		||||
326 -3.086790E-04  0.000000E+00
 | 
			
		||||
327 -1.986100E-04  0.000000E+00
 | 
			
		||||
328 -8.490010E-05  0.000000E+00
 | 
			
		||||
329  2.606830E-05  0.000000E+00
 | 
			
		||||
330  1.285060E-04  0.000000E+00
 | 
			
		||||
331  2.174900E-04  0.000000E+00
 | 
			
		||||
332  2.891620E-04  0.000000E+00
 | 
			
		||||
333  3.408650E-04  0.000000E+00
 | 
			
		||||
334  3.711900E-04  0.000000E+00
 | 
			
		||||
335  3.799620E-04  0.000000E+00
 | 
			
		||||
336  3.681570E-04  0.000000E+00
 | 
			
		||||
337  3.377580E-04  0.000000E+00
 | 
			
		||||
338  2.915810E-04  0.000000E+00
 | 
			
		||||
339  2.330560E-04  0.000000E+00
 | 
			
		||||
340  1.660030E-04  0.000000E+00
 | 
			
		||||
341  9.440430E-05  0.000000E+00
 | 
			
		||||
342  2.218660E-05  0.000000E+00
 | 
			
		||||
343 -4.697750E-05  0.000000E+00
 | 
			
		||||
344 -1.098340E-04  0.000000E+00
 | 
			
		||||
345 -1.636770E-04  0.000000E+00
 | 
			
		||||
346 -2.064400E-04  0.000000E+00
 | 
			
		||||
347 -2.367430E-04  0.000000E+00
 | 
			
		||||
348 -2.539050E-04  0.000000E+00
 | 
			
		||||
349 -2.579120E-04  0.000000E+00
 | 
			
		||||
350 -2.493630E-04  0.000000E+00
 | 
			
		||||
351 -2.293810E-04  0.000000E+00
 | 
			
		||||
352 -1.995190E-04  0.000000E+00
 | 
			
		||||
353 -1.616380E-04  0.000000E+00
 | 
			
		||||
354 -1.177970E-04  0.000000E+00
 | 
			
		||||
355 -7.013260E-05  0.000000E+00
 | 
			
		||||
356 -2.075750E-05  0.000000E+00
 | 
			
		||||
357  2.833800E-05  0.000000E+00
 | 
			
		||||
358  7.536150E-05  0.000000E+00
 | 
			
		||||
359  1.187790E-04  0.000000E+00
 | 
			
		||||
360  1.573550E-04  0.000000E+00
 | 
			
		||||
361  1.901760E-04  0.000000E+00
 | 
			
		||||
362  2.166500E-04  0.000000E+00
 | 
			
		||||
363  2.364990E-04  0.000000E+00
 | 
			
		||||
364  2.497330E-04  0.000000E+00
 | 
			
		||||
365  2.566110E-04  0.000000E+00
 | 
			
		||||
366  2.576020E-04  0.000000E+00
 | 
			
		||||
367  2.533370E-04  0.000000E+00
 | 
			
		||||
368  2.445590E-04  0.000000E+00
 | 
			
		||||
369  2.320800E-04  0.000000E+00
 | 
			
		||||
370  2.167350E-04  0.000000E+00
 | 
			
		||||
371  1.993440E-04  0.000000E+00
 | 
			
		||||
372  1.806800E-04  0.000000E+00
 | 
			
		||||
373  1.614470E-04  0.000000E+00
 | 
			
		||||
374  1.422580E-04  0.000000E+00
 | 
			
		||||
375  1.236260E-04  0.000000E+00
 | 
			
		||||
376  1.059620E-04  0.000000E+00
 | 
			
		||||
377  8.956970E-05  0.000000E+00
 | 
			
		||||
378  7.465790E-05  0.000000E+00
 | 
			
		||||
379  6.134480E-05  0.000000E+00
 | 
			
		||||
380  4.967160E-05  0.000000E+00
 | 
			
		||||
381  3.961540E-05  0.000000E+00
 | 
			
		||||
382  3.110180E-05  0.000000E+00
 | 
			
		||||
383  2.401920E-05  0.000000E+00
 | 
			
		||||
384  1.823040E-05  0.000000E+00
 | 
			
		||||
385  1.358420E-05  0.000000E+00
 | 
			
		||||
386  9.924440E-06  0.000000E+00
 | 
			
		||||
387  7.097790E-06  0.000000E+00
 | 
			
		||||
388  4.959500E-06  0.000000E+00
 | 
			
		||||
389  3.377490E-06  0.000000E+00
 | 
			
		||||
390  2.234920E-06  0.000000E+00
 | 
			
		||||
391  1.431250E-06  0.000000E+00
 | 
			
		||||
392  8.824160E-07  0.000000E+00
 | 
			
		||||
393  5.199920E-07  0.000000E+00
 | 
			
		||||
394  2.898420E-07  0.000000E+00
 | 
			
		||||
395  1.503850E-07  0.000000E+00
 | 
			
		||||
396  7.068940E-08  0.000000E+00
 | 
			
		||||
397  2.857200E-08  0.000000E+00
 | 
			
		||||
398  9.089140E-09  0.000000E+00
 | 
			
		||||
399 -1.280410E-09  0.000000E+00
 | 
			
		||||
							
								
								
									
										96
									
								
								lib/python/nettab/test/filters/scp_deci2.1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								lib/python/nettab/test/filters/scp_deci2.1
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,96 @@
 | 
			
		||||
  0 -4.624365e-06  0.000000e+00
 | 
			
		||||
  1 -8.258298e-05  0.000000e+00
 | 
			
		||||
  2 -2.260141e-04  0.000000e+00
 | 
			
		||||
  3 -2.539009e-04  0.000000e+00
 | 
			
		||||
  4  7.665667e-07  0.000000e+00
 | 
			
		||||
  5  3.050186e-04  0.000000e+00
 | 
			
		||||
  6  1.712792e-04  0.000000e+00
 | 
			
		||||
  7 -3.494469e-04  0.000000e+00
 | 
			
		||||
  8 -4.491013e-04  0.000000e+00
 | 
			
		||||
  9  2.631577e-04  0.000000e+00
 | 
			
		||||
 10  7.897725e-04  0.000000e+00
 | 
			
		||||
 11  3.857301e-05  0.000000e+00
 | 
			
		||||
 12 -1.091783e-03  0.000000e+00
 | 
			
		||||
 13 -5.999956e-04  0.000000e+00
 | 
			
		||||
 14  1.206435e-03  0.000000e+00
 | 
			
		||||
 15  1.397154e-03  0.000000e+00
 | 
			
		||||
 16 -9.624677e-04  0.000000e+00
 | 
			
		||||
 17 -2.313273e-03  0.000000e+00
 | 
			
		||||
 18  2.078273e-04  0.000000e+00
 | 
			
		||||
 19  3.130074e-03  0.000000e+00
 | 
			
		||||
 20  1.137016e-03  0.000000e+00
 | 
			
		||||
 21 -3.543348e-03  0.000000e+00
 | 
			
		||||
 22 -3.024242e-03  0.000000e+00
 | 
			
		||||
 23  3.207636e-03  0.000000e+00
 | 
			
		||||
 24  5.238007e-03  0.000000e+00
 | 
			
		||||
 25 -1.803839e-03  0.000000e+00
 | 
			
		||||
 26 -7.375909e-03  0.000000e+00
 | 
			
		||||
 27 -8.729728e-04  0.000000e+00
 | 
			
		||||
 28  8.870910e-03  0.000000e+00
 | 
			
		||||
 29  4.831847e-03  0.000000e+00
 | 
			
		||||
 30 -9.042305e-03  0.000000e+00
 | 
			
		||||
 31 -9.813905e-03  0.000000e+00
 | 
			
		||||
 32  7.179136e-03  0.000000e+00
 | 
			
		||||
 33  1.525300e-02  0.000000e+00
 | 
			
		||||
 34 -2.628732e-03  0.000000e+00
 | 
			
		||||
 35 -2.026759e-02  0.000000e+00
 | 
			
		||||
 36 -5.142914e-03  0.000000e+00
 | 
			
		||||
 37  2.366362e-02  0.000000e+00
 | 
			
		||||
 38  1.657857e-02  0.000000e+00
 | 
			
		||||
 39 -2.387548e-02  0.000000e+00
 | 
			
		||||
 40 -3.227953e-02  0.000000e+00
 | 
			
		||||
 41  1.860678e-02  0.000000e+00
 | 
			
		||||
 42  5.394208e-02  0.000000e+00
 | 
			
		||||
 43 -3.140518e-03  0.000000e+00
 | 
			
		||||
 44 -8.849621e-02  0.000000e+00
 | 
			
		||||
 45 -4.014856e-02  0.000000e+00
 | 
			
		||||
 46  1.847636e-01  0.000000e+00
 | 
			
		||||
 47  4.066011e-01  0.000000e+00
 | 
			
		||||
 48  4.066011e-01  0.000000e+00
 | 
			
		||||
 49  1.847636e-01  0.000000e+00
 | 
			
		||||
 50 -4.014856e-02  0.000000e+00
 | 
			
		||||
 51 -8.849621e-02  0.000000e+00
 | 
			
		||||
 52 -3.140518e-03  0.000000e+00
 | 
			
		||||
 53  5.394208e-02  0.000000e+00
 | 
			
		||||
 54  1.860678e-02  0.000000e+00
 | 
			
		||||
 55 -3.227953e-02  0.000000e+00
 | 
			
		||||
 56 -2.387548e-02  0.000000e+00
 | 
			
		||||
 57  1.657857e-02  0.000000e+00
 | 
			
		||||
 58  2.366362e-02  0.000000e+00
 | 
			
		||||
 59 -5.142914e-03  0.000000e+00
 | 
			
		||||
 60 -2.026759e-02  0.000000e+00
 | 
			
		||||
 61 -2.628732e-03  0.000000e+00
 | 
			
		||||
 62  1.525300e-02  0.000000e+00
 | 
			
		||||
 63  7.179136e-03  0.000000e+00
 | 
			
		||||
 64 -9.813905e-03  0.000000e+00
 | 
			
		||||
 65 -9.042305e-03  0.000000e+00
 | 
			
		||||
 66  4.831847e-03  0.000000e+00
 | 
			
		||||
 67  8.870910e-03  0.000000e+00
 | 
			
		||||
 68 -8.729728e-04  0.000000e+00
 | 
			
		||||
 69 -7.375909e-03  0.000000e+00
 | 
			
		||||
 70 -1.803839e-03  0.000000e+00
 | 
			
		||||
 71  5.238007e-03  0.000000e+00
 | 
			
		||||
 72  3.207636e-03  0.000000e+00
 | 
			
		||||
 73 -3.024242e-03  0.000000e+00
 | 
			
		||||
 74 -3.543348e-03  0.000000e+00
 | 
			
		||||
 75  1.137016e-03  0.000000e+00
 | 
			
		||||
 76  3.130074e-03  0.000000e+00
 | 
			
		||||
 77  2.078273e-04  0.000000e+00
 | 
			
		||||
 78 -2.313273e-03  0.000000e+00
 | 
			
		||||
 79 -9.624677e-04  0.000000e+00
 | 
			
		||||
 80  1.397154e-03  0.000000e+00
 | 
			
		||||
 81  1.206435e-03  0.000000e+00
 | 
			
		||||
 82 -5.999956e-04  0.000000e+00
 | 
			
		||||
 83 -1.091783e-03  0.000000e+00
 | 
			
		||||
 84  3.857301e-05  0.000000e+00
 | 
			
		||||
 85  7.897725e-04  0.000000e+00
 | 
			
		||||
 86  2.631577e-04  0.000000e+00
 | 
			
		||||
 87 -4.491013e-04  0.000000e+00
 | 
			
		||||
 88 -3.494469e-04  0.000000e+00
 | 
			
		||||
 89  1.712792e-04  0.000000e+00
 | 
			
		||||
 90  3.050186e-04  0.000000e+00
 | 
			
		||||
 91  7.665667e-07  0.000000e+00
 | 
			
		||||
 92 -2.539009e-04  0.000000e+00
 | 
			
		||||
 93 -2.260141e-04  0.000000e+00
 | 
			
		||||
 94 -8.258298e-05  0.000000e+00
 | 
			
		||||
 95 -4.624365e-06  0.000000e+00
 | 
			
		||||
							
								
								
									
										160
									
								
								lib/python/nettab/test/filters/scp_deci5.1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								lib/python/nettab/test/filters/scp_deci5.1
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,160 @@
 | 
			
		||||
  0  4.032461e-05  0.000000e+00
 | 
			
		||||
  1  7.453280e-05  0.000000e+00
 | 
			
		||||
  2  1.234553e-04  0.000000e+00
 | 
			
		||||
  3  1.701887e-04  0.000000e+00
 | 
			
		||||
  4  1.973105e-04  0.000000e+00
 | 
			
		||||
  5  1.854891e-04  0.000000e+00
 | 
			
		||||
  6  1.193456e-04  0.000000e+00
 | 
			
		||||
  7 -5.723101e-06  0.000000e+00
 | 
			
		||||
  8 -1.779232e-04  0.000000e+00
 | 
			
		||||
  9 -3.673259e-04  0.000000e+00
 | 
			
		||||
 10 -5.295104e-04  0.000000e+00
 | 
			
		||||
 11 -6.150085e-04  0.000000e+00
 | 
			
		||||
 12 -5.832354e-04  0.000000e+00
 | 
			
		||||
 13 -4.172837e-04  0.000000e+00
 | 
			
		||||
 14 -1.349516e-04  0.000000e+00
 | 
			
		||||
 15  2.083330e-04  0.000000e+00
 | 
			
		||||
 16  5.277090e-04  0.000000e+00
 | 
			
		||||
 17  7.281899e-04  0.000000e+00
 | 
			
		||||
 18  7.312587e-04  0.000000e+00
 | 
			
		||||
 19  5.019202e-04  0.000000e+00
 | 
			
		||||
 20  6.783176e-05  0.000000e+00
 | 
			
		||||
 21 -4.771493e-04  0.000000e+00
 | 
			
		||||
 22 -9.891580e-04  0.000000e+00
 | 
			
		||||
 23 -1.308918e-03  0.000000e+00
 | 
			
		||||
 24 -1.307358e-03  0.000000e+00
 | 
			
		||||
 25 -9.300168e-04  0.000000e+00
 | 
			
		||||
 26 -2.262541e-04  0.000000e+00
 | 
			
		||||
 27  6.483476e-04  0.000000e+00
 | 
			
		||||
 28  1.461708e-03  0.000000e+00
 | 
			
		||||
 29  1.963222e-03  0.000000e+00
 | 
			
		||||
 30  1.956625e-03  0.000000e+00
 | 
			
		||||
 31  1.367725e-03  0.000000e+00
 | 
			
		||||
 32  2.854628e-04  0.000000e+00
 | 
			
		||||
 33 -1.040387e-03  0.000000e+00
 | 
			
		||||
 34 -2.250679e-03  0.000000e+00
 | 
			
		||||
 35 -2.969069e-03  0.000000e+00
 | 
			
		||||
 36 -2.912737e-03  0.000000e+00
 | 
			
		||||
 37 -1.990583e-03  0.000000e+00
 | 
			
		||||
 38 -3.573537e-04  0.000000e+00
 | 
			
		||||
 39  1.598840e-03  0.000000e+00
 | 
			
		||||
 40  3.340972e-03  0.000000e+00
 | 
			
		||||
 41  4.323764e-03  0.000000e+00
 | 
			
		||||
 42  4.155636e-03  0.000000e+00
 | 
			
		||||
 43  2.736002e-03  0.000000e+00
 | 
			
		||||
 44  3.234310e-04  0.000000e+00
 | 
			
		||||
 45 -2.494752e-03  0.000000e+00
 | 
			
		||||
 46 -4.934943e-03  0.000000e+00
 | 
			
		||||
 47 -6.225197e-03  0.000000e+00
 | 
			
		||||
 48 -5.836136e-03  0.000000e+00
 | 
			
		||||
 49 -3.668966e-03  0.000000e+00
 | 
			
		||||
 50 -1.394092e-04  0.000000e+00
 | 
			
		||||
 51  3.880228e-03  0.000000e+00
 | 
			
		||||
 52  7.261232e-03  0.000000e+00
 | 
			
		||||
 53  8.919356e-03  0.000000e+00
 | 
			
		||||
 54  8.140252e-03  0.000000e+00
 | 
			
		||||
 55  4.837050e-03  0.000000e+00
 | 
			
		||||
 56 -3.434785e-04  0.000000e+00
 | 
			
		||||
 57 -6.115665e-03  0.000000e+00
 | 
			
		||||
 58 -1.084778e-02  0.000000e+00
 | 
			
		||||
 59 -1.299272e-02  0.000000e+00
 | 
			
		||||
 60 -1.154995e-02  0.000000e+00
 | 
			
		||||
 61 -6.430376e-03  0.000000e+00
 | 
			
		||||
 62  1.391199e-03  0.000000e+00
 | 
			
		||||
 63  1.000571e-02  0.000000e+00
 | 
			
		||||
 64  1.698057e-02  0.000000e+00
 | 
			
		||||
 65  1.997340e-02  0.000000e+00
 | 
			
		||||
 66  1.740665e-02  0.000000e+00
 | 
			
		||||
 67  9.029463e-03  0.000000e+00
 | 
			
		||||
 68 -3.794969e-03  0.000000e+00
 | 
			
		||||
 69 -1.818304e-02  0.000000e+00
 | 
			
		||||
 70 -3.022295e-02  0.000000e+00
 | 
			
		||||
 71 -3.578333e-02  0.000000e+00
 | 
			
		||||
 72 -3.146898e-02  0.000000e+00
 | 
			
		||||
 73 -1.550444e-02  0.000000e+00
 | 
			
		||||
 74  1.167237e-02  0.000000e+00
 | 
			
		||||
 75  4.726833e-02  0.000000e+00
 | 
			
		||||
 76  8.650819e-02  0.000000e+00
 | 
			
		||||
 77  1.234668e-01  0.000000e+00
 | 
			
		||||
 78  1.521942e-01  0.000000e+00
 | 
			
		||||
 79  1.678939e-01  0.000000e+00
 | 
			
		||||
 80  1.678939e-01  0.000000e+00
 | 
			
		||||
 81  1.521942e-01  0.000000e+00
 | 
			
		||||
 82  1.234668e-01  0.000000e+00
 | 
			
		||||
 83  8.650819e-02  0.000000e+00
 | 
			
		||||
 84  4.726833e-02  0.000000e+00
 | 
			
		||||
 85  1.167237e-02  0.000000e+00
 | 
			
		||||
 86 -1.550444e-02  0.000000e+00
 | 
			
		||||
 87 -3.146898e-02  0.000000e+00
 | 
			
		||||
 88 -3.578333e-02  0.000000e+00
 | 
			
		||||
 89 -3.022295e-02  0.000000e+00
 | 
			
		||||
 90 -1.818304e-02  0.000000e+00
 | 
			
		||||
 91 -3.794969e-03  0.000000e+00
 | 
			
		||||
 92  9.029463e-03  0.000000e+00
 | 
			
		||||
 93  1.740665e-02  0.000000e+00
 | 
			
		||||
 94  1.997340e-02  0.000000e+00
 | 
			
		||||
 95  1.698057e-02  0.000000e+00
 | 
			
		||||
 96  1.000571e-02  0.000000e+00
 | 
			
		||||
 97  1.391199e-03  0.000000e+00
 | 
			
		||||
 98 -6.430376e-03  0.000000e+00
 | 
			
		||||
 99 -1.154995e-02  0.000000e+00
 | 
			
		||||
100 -1.299272e-02  0.000000e+00
 | 
			
		||||
101 -1.084778e-02  0.000000e+00
 | 
			
		||||
102 -6.115665e-03  0.000000e+00
 | 
			
		||||
103 -3.434785e-04  0.000000e+00
 | 
			
		||||
104  4.837050e-03  0.000000e+00
 | 
			
		||||
105  8.140252e-03  0.000000e+00
 | 
			
		||||
106  8.919356e-03  0.000000e+00
 | 
			
		||||
107  7.261232e-03  0.000000e+00
 | 
			
		||||
108  3.880228e-03  0.000000e+00
 | 
			
		||||
109 -1.394092e-04  0.000000e+00
 | 
			
		||||
110 -3.668966e-03  0.000000e+00
 | 
			
		||||
111 -5.836136e-03  0.000000e+00
 | 
			
		||||
112 -6.225197e-03  0.000000e+00
 | 
			
		||||
113 -4.934943e-03  0.000000e+00
 | 
			
		||||
114 -2.494752e-03  0.000000e+00
 | 
			
		||||
115  3.234310e-04  0.000000e+00
 | 
			
		||||
116  2.736002e-03  0.000000e+00
 | 
			
		||||
117  4.155636e-03  0.000000e+00
 | 
			
		||||
118  4.323764e-03  0.000000e+00
 | 
			
		||||
119  3.340972e-03  0.000000e+00
 | 
			
		||||
120  1.598840e-03  0.000000e+00
 | 
			
		||||
121 -3.573537e-04  0.000000e+00
 | 
			
		||||
122 -1.990583e-03  0.000000e+00
 | 
			
		||||
123 -2.912737e-03  0.000000e+00
 | 
			
		||||
124 -2.969069e-03  0.000000e+00
 | 
			
		||||
125 -2.250679e-03  0.000000e+00
 | 
			
		||||
126 -1.040387e-03  0.000000e+00
 | 
			
		||||
127  2.854628e-04  0.000000e+00
 | 
			
		||||
128  1.367725e-03  0.000000e+00
 | 
			
		||||
129  1.956625e-03  0.000000e+00
 | 
			
		||||
130  1.963222e-03  0.000000e+00
 | 
			
		||||
131  1.461708e-03  0.000000e+00
 | 
			
		||||
132  6.483476e-04  0.000000e+00
 | 
			
		||||
133 -2.262541e-04  0.000000e+00
 | 
			
		||||
134 -9.300168e-04  0.000000e+00
 | 
			
		||||
135 -1.307358e-03  0.000000e+00
 | 
			
		||||
136 -1.308918e-03  0.000000e+00
 | 
			
		||||
137 -9.891580e-04  0.000000e+00
 | 
			
		||||
138 -4.771493e-04  0.000000e+00
 | 
			
		||||
139  6.783176e-05  0.000000e+00
 | 
			
		||||
140  5.019202e-04  0.000000e+00
 | 
			
		||||
141  7.312587e-04  0.000000e+00
 | 
			
		||||
142  7.281899e-04  0.000000e+00
 | 
			
		||||
143  5.277090e-04  0.000000e+00
 | 
			
		||||
144  2.083330e-04  0.000000e+00
 | 
			
		||||
145 -1.349516e-04  0.000000e+00
 | 
			
		||||
146 -4.172837e-04  0.000000e+00
 | 
			
		||||
147 -5.832354e-04  0.000000e+00
 | 
			
		||||
148 -6.150085e-04  0.000000e+00
 | 
			
		||||
149 -5.295104e-04  0.000000e+00
 | 
			
		||||
150 -3.673259e-04  0.000000e+00
 | 
			
		||||
151 -1.779232e-04  0.000000e+00
 | 
			
		||||
152 -5.723101e-06  0.000000e+00
 | 
			
		||||
153  1.193456e-04  0.000000e+00
 | 
			
		||||
154  1.854891e-04  0.000000e+00
 | 
			
		||||
155  1.973105e-04  0.000000e+00
 | 
			
		||||
156  1.701887e-04  0.000000e+00
 | 
			
		||||
157  1.234553e-04  0.000000e+00
 | 
			
		||||
158  7.453280e-05  0.000000e+00
 | 
			
		||||
159  4.032461e-05  0.000000e+00
 | 
			
		||||
							
								
								
									
										73
									
								
								lib/python/nettab/test/small-inst.db
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								lib/python/nettab/test/small-inst.db
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,73 @@
 | 
			
		||||
# Begin data logger list
 | 
			
		||||
#                        Gain   max.spfr mcld IIR(A,I)/FIR filter stages (not mandatory)
 | 
			
		||||
Ia: DigitizerModel="M24" M24-SC M24/BW
 | 
			
		||||
Ia: DigitizerModel="Q330" Q330/N Q330/HR Q330-SC
 | 
			
		||||
 | 
			
		||||
Ia: RecorderModel="M24" M24-SC M24/BW
 | 
			
		||||
Ia: RecorderModel="SeisComP" Q330-SC
 | 
			
		||||
Ia: RecorderModel="Q330" Q330/N Q330/HR
 | 
			
		||||
 | 
			
		||||
Ia: RecorderManufacturer="Quanterra" Q330/N Q330/HR
 | 
			
		||||
Ia: RecorderManufacturer="Lennartz" M24-SC M24/BW
 | 
			
		||||
Ia: RecorderManufacturer="Alpha2000" Q330-SC
 | 
			
		||||
 | 
			
		||||
Ia: DigitizerManufacturer="Quanterra" Q330/N Q330/HR Q330-SC
 | 
			
		||||
Ia: DigitizerManufacturer="Lennartz" M24-SC M24/BW
 | 
			
		||||
 | 
			
		||||
#                        Gain   max.spfr mcld IIR(A,I)/FIR filter stages (not mandatory)
 | 
			
		||||
Dl: Q330/N       419430.0    100.0 0.0 Q330  200,100_1,50_2,40_3,20_4,1_5,0.1_5/10
 | 
			
		||||
Dl: Q330/HR      1677720.0   100.0 0.0 Q330  100_1,50_2,40_3,20_4,1_5,0.1_5/10
 | 
			
		||||
Dl: Q330-SC      419430.0    100.0 0.0 Q330  100_1,50_1/6,20_1/7,1_1/7/8/9,0.1_1/7/8/9/10
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# End data logger list
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# FIR filter list for Quanterra Q330 digitizer and Seiscomp recorder
 | 
			
		||||
#                Name      Sym ncf  inrate fac delay    corrtn  gain frg
 | 
			
		||||
Ff: Q330_FIR_1 q330_b100_100   A  65 0  100.0  1  0.041607  0.041607 1.0 0.0
 | 
			
		||||
Ff: Q330_FIR_2 q330_b100_50    A  81 0   50.0  1  0.531607  0.531607 1.0 0.0
 | 
			
		||||
Ff: Q330_FIR_3 q330_b100_40    A  39 0   40.0  1  0.430462  0.430462 1.0 0.0
 | 
			
		||||
Ff: Q330_FIR_4 q330_b100_20    A  67 0   20.0  1  1.630462  1.630462 1.0 0.0
 | 
			
		||||
Ff: Q330_FIR_5 q330_b100_1     A  31 0    1.0  1 15.930462 15.930462 1.0 0.0
 | 
			
		||||
Ff: Q330_FIR_6 scp_deci2.1     C  48 0  100.0  2  0.000     0.0      1.0 0.0
 | 
			
		||||
Ff: Q330_FIR_7 scp_deci5.1     C  80 0  100.0  5  0.000     0.0      1.0 0.0
 | 
			
		||||
Ff: Q330_FIR_8 scp_deci2.1     C  48 0   20.0  2  0.000     0.0      1.0 0.0
 | 
			
		||||
Ff: Q330_FIR_9 scp_deci10.1    C 200 0   10.0 10  0.000     0.0      1.0 0.0
 | 
			
		||||
Ff: Q330_FIR_10 scp_deci10.1   C 200 0    1.0 10  0.000     0.0      4.0 0.0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Digitizer IIR filter response list
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# Digitizer analog response list
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Begin seismometer list
 | 
			
		||||
# Seismometer analog response list
 | 
			
		||||
#              .        Gain  frgn Norm.fac fnr  nz np Zeros&Poles
 | 
			
		||||
#   Sensor type: VBB
 | 
			
		||||
Ia: Model="STS-2/CZ" STS-2/CZ
 | 
			
		||||
Ia: Model="STS-2/N" STS-2/N
 | 
			
		||||
Ia: Model="STS-2/G2" STS-2/G2
 | 
			
		||||
Ia: Model="STS-2/HG" STS-2/HG
 | 
			
		||||
Ia: Model="STS-2/G1" STS-2/G1
 | 
			
		||||
Ia: Model="STS-2/G3" STS-2/G3
 | 
			
		||||
 | 
			
		||||
Ia: Type="VBB" STS-2/CZ STS-2/N STS-2/G2 STS-2/HG STS-2/G3 STS-2/G1
 | 
			
		||||
 | 
			
		||||
Ia: Unit="M/S" STS-2/CZ STS-2/N STS-2/G2 STS-2/HG STS-2/G3 STS-2/G1
 | 
			
		||||
 | 
			
		||||
Ia: Manufacturer="Streckeisen" STS-2/CZ STS-2/N STS-2/G2 STS-2/HG STS-2/G3 STS-2/G1
 | 
			
		||||
 | 
			
		||||
Se: STS-2/N           1500.0 0.02 6.0077e7 1.0  2  5  2(0.0,0.0) (-0.037004,0.037016)  (-0.037004,-0.037016) (-251.33,0.0) (-131.04,-467.29) (-131.04,467.29)
 | 
			
		||||
Se: STS-2/G1          1500.0 0.02 3.46844e17 1.0 5 9  2(0.0,0.0) (-15.15,0.0) (-318.6,401.2) (-318.6,-401.2) (-0.037,0.037) (-0.037,-0.037) (-15.99,0.0) (-100.9,401.9) (-100.9,-401.9) (-187.2,0.0) (-417.1,0.0) (-7454.0,7142.0) (-7454.0,-7142.0)
 | 
			
		||||
Se: STS-2/G2          1500.0 0.02 3.46844e17 1.0 9 14 2(0.0,0.0) (-10.75,0.0) (-294.6,0.0) (-555.1,0.0) (-683.9,175.5) (-683.9,-175.5) (-5907.0,3411.0) (-5907.0,-3411.0) (-0.037,0.037) (-0.037,-0.037) (-10.95,0.0) (-98.44,442.8) (-98.44,-442.8) (-251.1,0.0) (-556.8,60.0) (-556.8,-60.0) (-1391.0,0.0) (-4936.0,4713.0) (-4936.0,-4713.0) (-6227.0,0.0) (-6909.0,9208.0) (-6909.0,-9208.0)
 | 
			
		||||
Se: STS-2/G3          1500.0 0.02 3.46844e17 1.0 6 11 2(0.0,0.0) (-15.15,0.0) (-176.6,0.0) (-463.1,430.5) (-463.1,-430.5) (-0.037,0.037) (-0.037,-0.037) (-15.64,0.0) (-97.34,-400.7) (-97.34,400.7) (-255.1,0.0) (-374.8,0.0) (-520.3,0.0) (-10530.,10050.) (-10530.,-10050.) (-13300.,0.0)
 | 
			
		||||
#Streckeisen_STS-2/HG> 20000.0 0.02 3.46844e17 1.0 6 11 2(0.0,0.0) (-15.15,0.0) (-176.6,0.0) (-463.1,430.5) (-463.1,430.5) (-0.037,0.037) (-0.037,-0.037) (-15.64,0.0) (-97.34,-400.7) (-97.34,400.7) (-255.1,0.0) (-374.8,0.0) (-520.3,0.0) (-10530.,10050.) (-10530.,-10050.) (-13300.,0.0)
 | 
			
		||||
#Streckeisen_STS-2/CZ>  1500.0  1.0 4.47172e2 1.0 6  7  2(0.0,0.0) (-15.1488,0.0) (-199.554,0.0) (-461.814,429.079) (-461.814,-429.079) (-0.03702,0.03702) (-0.03702,-0.03702) (-15.2744,0.0) (-82.8124,409.852) (-82.8124,-409.852) (-443.314,0.0) (-454.526,0.0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# End seismometer list
 | 
			
		||||
							
								
								
									
										291
									
								
								lib/python/nettab/test/testTab.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								lib/python/nettab/test/testTab.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,291 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
 | 
			
		||||
###############################################################################
 | 
			
		||||
# Copyright (C) 2020 Helmholtz-Zentrum Potsdam - Deutsches
 | 
			
		||||
#    GeoForschungsZentrum GFZ
 | 
			
		||||
#
 | 
			
		||||
# License: GPL Affero General Public License (GNU AGPL) version 3.0
 | 
			
		||||
# Author:  Peter L. Evans
 | 
			
		||||
# E-mail:  <pevans@gfz-potsdam.de>
 | 
			
		||||
#
 | 
			
		||||
###############################################################################
 | 
			
		||||
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
 | 
			
		||||
from nettab.tab import Tab
 | 
			
		||||
import json
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import tempfile
 | 
			
		||||
import unittest
 | 
			
		||||
 | 
			
		||||
# Just to dump XML output??:
 | 
			
		||||
try:
 | 
			
		||||
    import seiscomp.io as IO
 | 
			
		||||
except ImportError:
 | 
			
		||||
    print('Failed to import seiscomp.io module, trying seiscomp3.IO instead')
 | 
			
		||||
    from seiscomp3 import IO
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Just to examine the output XML:
 | 
			
		||||
import xml.etree.ElementTree as ET
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def xmlparse(filename):
 | 
			
		||||
    parser = ET.XMLParser()
 | 
			
		||||
    try:
 | 
			
		||||
        parser.feed(open(filename).read())
 | 
			
		||||
    except Exception:
 | 
			
		||||
        raise
 | 
			
		||||
    elem = parser.close()
 | 
			
		||||
    ns = '{http://geofon.gfz-potsdam.de/ns/seiscomp3-schema/0.11}'
 | 
			
		||||
    return (elem, ns)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestTab(unittest.TestCase):
 | 
			
		||||
    simpleTab = '''
 | 
			
		||||
Nw: QQ 2020-04-01
 | 
			
		||||
Na: Description="Atlantis Seismic Network"
 | 
			
		||||
Sl: AA01  "Pillars of Hercules/Atlantis"    Q330/N%xxxx    STS-2/N%yyyy    100/20    ZNE 30.0  -15.0  -900  2.0  2020-04-02
 | 
			
		||||
'''
 | 
			
		||||
 | 
			
		||||
    tabWithPid = '''
 | 
			
		||||
Nw: QQ 2020-04-01
 | 
			
		||||
Na: Description="Atlantis Seismic Network"
 | 
			
		||||
Na: Pid="doi:10.1234/xyz"
 | 
			
		||||
Sl: AA01  "Pillars of Hercules/Atlantis"    Q330/N%xxxx    STS-2/N%yyyy    100/20    ZNE 30.0  -15.0  -900  2.0  2020-04-02
 | 
			
		||||
'''
 | 
			
		||||
 | 
			
		||||
    instFile = 'small-inst.db'
 | 
			
		||||
 | 
			
		||||
    templateTab = '''
 | 
			
		||||
Nw: {nwline}
 | 
			
		||||
Na: {naline}
 | 
			
		||||
Sl: {slline}
 | 
			
		||||
'''
 | 
			
		||||
 | 
			
		||||
    def _writeTempTab(self, tabText):
 | 
			
		||||
        '''Put a nettab formatted string into a temporary file,
 | 
			
		||||
        returning the file name.
 | 
			
		||||
        '''
 | 
			
		||||
        with tempfile.NamedTemporaryFile(delete=False) as tab:
 | 
			
		||||
            print(tabText, file=tab)
 | 
			
		||||
        tab.close()
 | 
			
		||||
        return tab.name
 | 
			
		||||
 | 
			
		||||
    def _writeInvXML(self, inv, filename='something.xml'):
 | 
			
		||||
        '''Copied from tab2inv.py'''
 | 
			
		||||
        ar = IO.XMLArchive()
 | 
			
		||||
        print("Generating file: %s" % filename,
 | 
			
		||||
              file=sys.stderr)
 | 
			
		||||
        ar.create(filename)
 | 
			
		||||
        ar.setFormattedOutput(True)
 | 
			
		||||
        ar.setCompression(False)
 | 
			
		||||
        ar.writeObject(inv)
 | 
			
		||||
        ar.close()
 | 
			
		||||
 | 
			
		||||
    def _writeNewInvXML(self, sc3inv, filename):
 | 
			
		||||
        try:
 | 
			
		||||
            os.unlink(filename)
 | 
			
		||||
        except OSError:  # Python3: Catch FileNotFoundError instead.
 | 
			
		||||
            pass
 | 
			
		||||
        self._writeInvXML(sc3inv, filename)
 | 
			
		||||
 | 
			
		||||
    def test_1(self):
 | 
			
		||||
        '''Create object'''
 | 
			
		||||
        t = Tab()
 | 
			
		||||
        print('Expect: "Warning, not filter folder supplied."',
 | 
			
		||||
              file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
    def test_2_filter(self):
 | 
			
		||||
        '''Provide a (trivial, non-useful) filter folder'''
 | 
			
		||||
        t = Tab(None, None, '.', None, None)
 | 
			
		||||
 | 
			
		||||
    def test_2_defaults_warning(self):
 | 
			
		||||
        '''Provide and load a defaults file'''
 | 
			
		||||
        defaults = tempfile.NamedTemporaryFile(delete=False)
 | 
			
		||||
        print('''
 | 
			
		||||
Nw: QQ 2001/001
 | 
			
		||||
        ''', file=defaults)
 | 
			
		||||
        defaultsFile = defaults.name
 | 
			
		||||
        defaults.close()
 | 
			
		||||
        t = Tab(None, defaultsFile, '.', None, None)
 | 
			
		||||
        os.unlink(defaultsFile)
 | 
			
		||||
        print("Expect: 'Warning: Defaults file can only contain attributes'",
 | 
			
		||||
              file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
    def test_2_defaults_attributes(self):
 | 
			
		||||
        '''Provide and load a defaults file'''
 | 
			
		||||
        defaults = tempfile.NamedTemporaryFile(delete=False)
 | 
			
		||||
        print('''
 | 
			
		||||
Na: Foo=bar
 | 
			
		||||
Sa: StationFoo=bla * *
 | 
			
		||||
Ia: InstrumentFoo=blu *
 | 
			
		||||
        ''', file=defaults)
 | 
			
		||||
        defaultsFile = defaults.name
 | 
			
		||||
        defaults.close()
 | 
			
		||||
        t = Tab(None, defaultsFile, '.', None, None)
 | 
			
		||||
        os.unlink(defaultsFile)
 | 
			
		||||
 | 
			
		||||
    def test_3_digest(self):
 | 
			
		||||
        tabFile = self._writeTempTab(self.simpleTab)
 | 
			
		||||
 | 
			
		||||
        t = Tab(None, None, '.', None, None)
 | 
			
		||||
        t.digest(tabFile)
 | 
			
		||||
        os.unlink(tabFile)
 | 
			
		||||
 | 
			
		||||
    def SKIPtest_3_digest_check(self):
 | 
			
		||||
        tabFile = self._writeTempTab(self.simpleTab)
 | 
			
		||||
 | 
			
		||||
        t = Tab(None, None, 'filters', None, None)
 | 
			
		||||
        t.digest(tabFile)
 | 
			
		||||
        t.digest(self.instFile)
 | 
			
		||||
        t.check()
 | 
			
		||||
        os.unlink(tabFile)
 | 
			
		||||
 | 
			
		||||
    def test_4_digest_twice(self):
 | 
			
		||||
        '''Exception is raised by digesting twice.'''
 | 
			
		||||
        tabFile = self._writeTempTab(self.simpleTab)
 | 
			
		||||
 | 
			
		||||
        t = Tab(None, None, '.', None, None)
 | 
			
		||||
        t.digest(tabFile)
 | 
			
		||||
        with self.assertRaises(Exception):
 | 
			
		||||
            t.digest(tabFile)
 | 
			
		||||
        # print('Expect: "Warning: File {name}  is already digested."')
 | 
			
		||||
 | 
			
		||||
        os.unlink(tabFile)
 | 
			
		||||
 | 
			
		||||
    def test_5_na_after_sa(self):
 | 
			
		||||
        '''Not allowed to provide Na lines after a Sl line'''
 | 
			
		||||
        s = '\n'.join([self.simpleTab, 'Na: Pid=10.123/xyz'])
 | 
			
		||||
        tabFile = self._writeTempTab(s)
 | 
			
		||||
 | 
			
		||||
        with self.assertRaises(Exception):
 | 
			
		||||
            t.digest(tabFile)
 | 
			
		||||
        # print('Expect "No Na lines after a Sl line.',
 | 
			
		||||
        #       'Network has already been defined."')
 | 
			
		||||
        os.unlink(tabFile)
 | 
			
		||||
 | 
			
		||||
    def test_6_network_pid(self):
 | 
			
		||||
        '''Key 'Pid' is an allowed network attribute'''
 | 
			
		||||
        tabString = '''
 | 
			
		||||
Nw: QQ 2001/001
 | 
			
		||||
Na: Region=Atlantis
 | 
			
		||||
Na: Pid=10.123/xyz
 | 
			
		||||
'''
 | 
			
		||||
        tabFile = self._writeTempTab(tabString)
 | 
			
		||||
 | 
			
		||||
        t = Tab(None, None, '.', None, None)
 | 
			
		||||
        t.digest(tabFile)
 | 
			
		||||
        os.unlink(tabFile)
 | 
			
		||||
 | 
			
		||||
    def test_6_network_pid_check(self):
 | 
			
		||||
        '''No problem to define extra unhandled attributes'''
 | 
			
		||||
        tabString = '''
 | 
			
		||||
Nw: QQ 2001/001
 | 
			
		||||
Na: Region=Atlantis
 | 
			
		||||
Na: Pid=10.123/xyz
 | 
			
		||||
Na: Foo=bar
 | 
			
		||||
'''
 | 
			
		||||
        tabFile = self._writeTempTab(tabString)
 | 
			
		||||
 | 
			
		||||
        t = Tab(None, None, '.', None, None)
 | 
			
		||||
        t.digest(tabFile)
 | 
			
		||||
        t.check()
 | 
			
		||||
        os.unlink(tabFile)
 | 
			
		||||
 | 
			
		||||
    def test_7_sc3Obj(self):
 | 
			
		||||
        '''Call sc3Obj with a trivial t'''
 | 
			
		||||
        t = Tab(None, None, '.', None, None)
 | 
			
		||||
        sc3inv = t.sc3Obj()
 | 
			
		||||
 | 
			
		||||
    def test_8_network_sc3Obj(self):
 | 
			
		||||
        '''Call sc3Obj with an actual network, write XML'''
 | 
			
		||||
        tabFile = self._writeTempTab(self.simpleTab)
 | 
			
		||||
 | 
			
		||||
        t = Tab(None, None, 'filters', None, None)
 | 
			
		||||
        t.digest(tabFile)
 | 
			
		||||
        t.digest(self.instFile)
 | 
			
		||||
        sc3inv = t.sc3Obj()
 | 
			
		||||
        # Returns ok, but reports inst.db errors and warnings to stdout.
 | 
			
		||||
        self.assertTrue(sc3inv)
 | 
			
		||||
        if sc3inv is None:
 | 
			
		||||
            assert('scinv is None')
 | 
			
		||||
        sc3inv
 | 
			
		||||
        outFile = '/tmp/testTabInv.xml'
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            os.unlink(outFile)
 | 
			
		||||
        except OSError:  # Python3: Catch FileNotFoundError instead.
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        self._writeInvXML(sc3inv, filename=outFile)
 | 
			
		||||
        self.assertTrue(os.path.exists(outFile))
 | 
			
		||||
        # Further checks: that the file contains a network, etc.
 | 
			
		||||
 | 
			
		||||
    def test_9_network_pid_sc3Obj(self):
 | 
			
		||||
        '''Load a network with PID, write XML, confirm PID is there.
 | 
			
		||||
        Older nettabs reported 'ignoring attribute Pid'.
 | 
			
		||||
        '''
 | 
			
		||||
        tabFile = self._writeTempTab(self.tabWithPid)
 | 
			
		||||
 | 
			
		||||
        t = Tab(None, None, 'filters', None, None)
 | 
			
		||||
        t.digest(tabFile)
 | 
			
		||||
        t.digest(self.instFile)
 | 
			
		||||
        sc3inv = t.sc3Obj()
 | 
			
		||||
        self.assertTrue(sc3inv)
 | 
			
		||||
 | 
			
		||||
        outFile = '/tmp/testTabInvPid.xml'
 | 
			
		||||
        self._writeNewInvXML(sc3inv, outFile)
 | 
			
		||||
        self.assertTrue(os.path.exists(outFile))
 | 
			
		||||
 | 
			
		||||
        # Check that the file contains exactly one network comment
 | 
			
		||||
        # which is a JSON string with PID.
 | 
			
		||||
        # e.g. '{"type": "DOI", "value": "10.1234/xsdfa"}'
 | 
			
		||||
        (elem, ns) = xmlparse(outFile)
 | 
			
		||||
        for e in elem:
 | 
			
		||||
            for f in e:
 | 
			
		||||
                if f.tag == ns + 'network':
 | 
			
		||||
                    g = f.findall(ns + 'comment')
 | 
			
		||||
                    self.assertTrue(len(g) == 1)
 | 
			
		||||
                    t = g[0].findall(ns + 'text')
 | 
			
		||||
                    text = t[0].text
 | 
			
		||||
                    j = json.loads(t[0].text)
 | 
			
		||||
                    self.assertEqual(j['type'], 'DOI')
 | 
			
		||||
                    self.assertEqual(j['value'], '10.1234/xyz')
 | 
			
		||||
                    ### self.assertEqual(t[0].text, 'doi:10.1234/xyz')
 | 
			
		||||
 | 
			
		||||
    def test_10_network_comment(self):
 | 
			
		||||
        tabString = '''
 | 
			
		||||
Nw: NN 2020/092
 | 
			
		||||
Na: Region=Atlantis
 | 
			
		||||
Na: Comment="This is commentary"
 | 
			
		||||
Na: Remark="Remarkable!"
 | 
			
		||||
Sl: AA01  "Zeus"  Q330/N%xxxx  STS-2/N%yyyy  20  Z  30 -15  -2  2.0 2020/093
 | 
			
		||||
'''
 | 
			
		||||
        tabFile = self._writeTempTab(tabString)
 | 
			
		||||
        t = Tab(None, None, 'filters', None, None)
 | 
			
		||||
        t.digest(tabFile)
 | 
			
		||||
        t.digest(self.instFile)
 | 
			
		||||
        t.check()
 | 
			
		||||
        os.unlink(tabFile)
 | 
			
		||||
 | 
			
		||||
        sc3inv = t.sc3Obj()
 | 
			
		||||
        self.assertTrue(sc3inv)
 | 
			
		||||
        outFile = '/tmp/testTabInvComment.xml'
 | 
			
		||||
        self._writeNewInvXML(sc3inv, '/tmp/testTabInvComment.xml')
 | 
			
		||||
        self.assertTrue(os.path.exists(outFile))
 | 
			
		||||
 | 
			
		||||
        # Further checks: that the file contains a network with PID. TODO
 | 
			
		||||
        (elem, ns) = xmlparse(outFile)
 | 
			
		||||
        for e in elem:
 | 
			
		||||
            for f in e:
 | 
			
		||||
                if f.tag == ns + 'network':
 | 
			
		||||
                    g = f.findall(ns + 'comment')
 | 
			
		||||
                    self.assertTrue(len(g) == 1)
 | 
			
		||||
                    # DEBUG print('DEBUG Network comment found:',
 | 
			
		||||
                    #       g[0].findall(ns + 'text')[0].text)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    unittest.main(verbosity=1)
 | 
			
		||||
							
								
								
									
										4
									
								
								lib/python/seiscomp/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								lib/python/seiscomp/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
sys.setdlopenflags(os.RTLD_LAZY | os.RTLD_GLOBAL)
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								lib/python/seiscomp/_client.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/python/seiscomp/_client.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/python/seiscomp/_config.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/python/seiscomp/_config.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/python/seiscomp/_core.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/python/seiscomp/_core.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/python/seiscomp/_geo.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/python/seiscomp/_geo.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/python/seiscomp/_io.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/python/seiscomp/_io.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/python/seiscomp/_logging.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/python/seiscomp/_logging.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/python/seiscomp/_math.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/python/seiscomp/_math.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/python/seiscomp/_seismology.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/python/seiscomp/_seismology.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/python/seiscomp/_system.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/python/seiscomp/_system.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/python/seiscomp/_utils.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/python/seiscomp/_utils.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										560
									
								
								lib/python/seiscomp/bindings2cfg.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										560
									
								
								lib/python/seiscomp/bindings2cfg.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,560 @@
 | 
			
		||||
############################################################################
 | 
			
		||||
# Copyright (C) gempa GmbH                                                 #
 | 
			
		||||
# All rights reserved.                                                     #
 | 
			
		||||
# Contact: gempa GmbH (seiscomp-dev@gempa.de)                              #
 | 
			
		||||
#                                                                          #
 | 
			
		||||
# GNU Affero General Public License Usage                                  #
 | 
			
		||||
# This file may be used under the terms of the GNU Affero                  #
 | 
			
		||||
# Public License version 3.0 as published by the Free Software Foundation  #
 | 
			
		||||
# and appearing in the file LICENSE included in the packaging of this      #
 | 
			
		||||
# file. Please review the following information to ensure the GNU Affero   #
 | 
			
		||||
# Public License version 3.0 requirements will be met:                     #
 | 
			
		||||
# https://www.gnu.org/licenses/agpl-3.0.html.                              #
 | 
			
		||||
#                                                                          #
 | 
			
		||||
# Other Usage                                                              #
 | 
			
		||||
# Alternatively, this file may be used in accordance with the terms and    #
 | 
			
		||||
# conditions contained in a signed written agreement between you and       #
 | 
			
		||||
# gempa GmbH.                                                              #
 | 
			
		||||
############################################################################
 | 
			
		||||
 | 
			
		||||
import os, time, sys
 | 
			
		||||
import seiscomp.core, seiscomp.client, seiscomp.datamodel
 | 
			
		||||
import seiscomp.io, seiscomp.system
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def collectParams(container):
 | 
			
		||||
    params = {}
 | 
			
		||||
    for i in range(container.groupCount()):
 | 
			
		||||
        params.update(collectParams(container.group(i)))
 | 
			
		||||
    for i in range(container.structureCount()):
 | 
			
		||||
        params.update(collectParams(container.structure(i)))
 | 
			
		||||
    for i in range(container.parameterCount()):
 | 
			
		||||
        p = container.parameter(i)
 | 
			
		||||
        if p.symbol.stage == seiscomp.system.Environment.CS_UNDEFINED:
 | 
			
		||||
            continue
 | 
			
		||||
        params[p.variableName] = ",".join(p.symbol.values)
 | 
			
		||||
 | 
			
		||||
    return params
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def collect(idset, paramSetID):
 | 
			
		||||
    paramSet = seiscomp.datamodel.ParameterSet.Find(paramSetID)
 | 
			
		||||
    if not paramSet:
 | 
			
		||||
        return
 | 
			
		||||
    idset[paramSet.publicID()] = 1
 | 
			
		||||
    if not paramSet.baseID():
 | 
			
		||||
        return
 | 
			
		||||
    collect(idset, paramSet.baseID())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def sync(paramSet, params):
 | 
			
		||||
    obsoleteParams = []
 | 
			
		||||
    seenParams = {}
 | 
			
		||||
    i = 0
 | 
			
		||||
    while i < paramSet.parameterCount():
 | 
			
		||||
        p = paramSet.parameter(i)
 | 
			
		||||
        if p.name() in params:
 | 
			
		||||
            if p.name() in seenParams:
 | 
			
		||||
                # Multiple parameter definitions with same name
 | 
			
		||||
                sys.stderr.write(
 | 
			
		||||
                    f"- {p.publicID()}:{p.name()} / duplicate parameter name\n"
 | 
			
		||||
                )
 | 
			
		||||
                p.detach()
 | 
			
		||||
                continue
 | 
			
		||||
            seenParams[p.name()] = 1
 | 
			
		||||
            val = params[p.name()]
 | 
			
		||||
            if val != p.value():
 | 
			
		||||
                p.setValue(val)
 | 
			
		||||
                p.update()
 | 
			
		||||
        else:
 | 
			
		||||
            obsoleteParams.append(p)
 | 
			
		||||
        i = i + 1
 | 
			
		||||
 | 
			
		||||
    for p in obsoleteParams:
 | 
			
		||||
        p.detach()
 | 
			
		||||
 | 
			
		||||
    for key, val in list(params.items()):
 | 
			
		||||
        if key in seenParams:
 | 
			
		||||
            continue
 | 
			
		||||
        p = seiscomp.datamodel.Parameter.Create()
 | 
			
		||||
        p.setName(key)
 | 
			
		||||
        p.setValue(val)
 | 
			
		||||
        paramSet.add(p)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConfigDBUpdater(seiscomp.client.Application):
 | 
			
		||||
    def __init__(self, argc, argv):
 | 
			
		||||
        seiscomp.client.Application.__init__(self, argc, argv)
 | 
			
		||||
        self.setLoggingToStdErr(True)
 | 
			
		||||
        self.setMessagingEnabled(True)
 | 
			
		||||
        self.setDatabaseEnabled(True, True)
 | 
			
		||||
        self.setAutoApplyNotifierEnabled(False)
 | 
			
		||||
        self.setInterpretNotifierEnabled(False)
 | 
			
		||||
        self.setMessagingUsername("_sccfgupd_")
 | 
			
		||||
        self.setLoadConfigModuleEnabled(True)
 | 
			
		||||
        # Load all configuration modules
 | 
			
		||||
        self.setConfigModuleName("")
 | 
			
		||||
        self.setPrimaryMessagingGroup(seiscomp.client.Protocol.LISTENER_GROUP)
 | 
			
		||||
 | 
			
		||||
        self._moduleName = None
 | 
			
		||||
        self._outputFile = None
 | 
			
		||||
        self._createNotifier = False
 | 
			
		||||
        self._keyDir = None
 | 
			
		||||
 | 
			
		||||
    def createCommandLineDescription(self):
 | 
			
		||||
        self.commandline().addGroup("Input")
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Input",
 | 
			
		||||
            "key-dir",
 | 
			
		||||
            "Overrides the location of the default key directory ($SEISCOMP_ROOT/etc/key)",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addGroup("Output")
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Output", "module-name", "The module name to be used for the config module. If not given then the application name is being used or 'trunk' if output to a file is enabled"
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Output", "output,o", "If given, an output XML file is generated"
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addOption(
 | 
			
		||||
            "Output", "create-notifier", "If given then a notifier message containing all notifiers "
 | 
			
		||||
            "will be written to the output XML. This option only applies "
 | 
			
		||||
            "if an output file is given. Notifier creation either requires "
 | 
			
		||||
            "and input database and an input config XML as reference."
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def validateParameters(self):
 | 
			
		||||
        if not seiscomp.client.Application.validateParameters(self):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._moduleName = self.commandline().optionString("module-name")
 | 
			
		||||
        except:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._outputFile = self.commandline().optionString("output")
 | 
			
		||||
            self._createNotifier = self.commandline().hasOption("create-notifier")
 | 
			
		||||
            # Switch to offline mode
 | 
			
		||||
            self.setMessagingEnabled(False)
 | 
			
		||||
            self.setDatabaseEnabled(False, False)
 | 
			
		||||
            if self._createNotifier:
 | 
			
		||||
                if self.isConfigDatabaseEnabled() == True:
 | 
			
		||||
                    self.setDatabaseEnabled(True, False);
 | 
			
		||||
            else:
 | 
			
		||||
                self.setLoadConfigModuleEnabled(False)
 | 
			
		||||
        except:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._keyDir = self.commandline().optionString("key-dir")
 | 
			
		||||
        except:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def init(self):
 | 
			
		||||
        if not seiscomp.client.Application.init(self):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        # Initialize the basic directories
 | 
			
		||||
        filebase = seiscomp.system.Environment.Instance().installDir()
 | 
			
		||||
        descdir = os.path.join(filebase, "etc", "descriptions")
 | 
			
		||||
 | 
			
		||||
        # Load definitions of the configuration schema
 | 
			
		||||
        defs = seiscomp.system.SchemaDefinitions()
 | 
			
		||||
        if not defs.load(descdir):
 | 
			
		||||
            print("Error: could not read descriptions", file=sys.stderr)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if defs.moduleCount() == 0:
 | 
			
		||||
            print("Warning: no modules defined, nothing to do", file=sys.stderr)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        # Create a model from the schema and read its configuration including
 | 
			
		||||
        # all bindings.
 | 
			
		||||
        model = seiscomp.system.Model()
 | 
			
		||||
        if self._keyDir:
 | 
			
		||||
            model.keyDirOverride = self._keyDir
 | 
			
		||||
        model.create(defs)
 | 
			
		||||
        model.readConfig()
 | 
			
		||||
 | 
			
		||||
        # Find all binding mods for trunk. Bindings of modules where standalone
 | 
			
		||||
        # is set to true are ignored. They are supposed to handle their bindings
 | 
			
		||||
        # on their own.
 | 
			
		||||
        self.bindingMods = []
 | 
			
		||||
        for i in range(defs.moduleCount()):
 | 
			
		||||
            mod = defs.module(i)
 | 
			
		||||
            # Ignore stand alone modules (eg seedlink, slarchive, ...) as they
 | 
			
		||||
            # are not using the trunk libraries and don't need database
 | 
			
		||||
            # configurations
 | 
			
		||||
            if mod.isStandalone():
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            self.bindingMods.append(mod.name)
 | 
			
		||||
 | 
			
		||||
        if len(self.bindingMods) == 0:
 | 
			
		||||
            print("Warning: no usable modules found, nothing to do", file=sys.stderr)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        self.stationSetups = {}
 | 
			
		||||
 | 
			
		||||
        # Read bindings
 | 
			
		||||
        for m in self.bindingMods:
 | 
			
		||||
            mod = model.module(m)
 | 
			
		||||
            if not mod:
 | 
			
		||||
                print(f"Warning: module {m} not assigned", file=sys.stderr)
 | 
			
		||||
                continue
 | 
			
		||||
            if len(mod.bindings) == 0:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            if len(m) > 20:
 | 
			
		||||
                print(
 | 
			
		||||
                    f"Error: rejecting module {m} - name is longer than 20 characters",
 | 
			
		||||
                    file=sys.stderr,
 | 
			
		||||
                )
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
            # Rename global to default for being compatible with older
 | 
			
		||||
            # releases
 | 
			
		||||
            if m == "global":
 | 
			
		||||
                m = "default"
 | 
			
		||||
 | 
			
		||||
            print(f"+ {m}", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
            for staid in list(mod.bindings.keys()):
 | 
			
		||||
                binding = mod.getBinding(staid)
 | 
			
		||||
                if not binding:
 | 
			
		||||
                    continue
 | 
			
		||||
                # sys.stderr.write("  + %s.%s\n" % (staid.networkCode, staid.stationCode))
 | 
			
		||||
                params = {}
 | 
			
		||||
                for i in range(binding.sectionCount()):
 | 
			
		||||
                    params.update(collectParams(binding.section(i)))
 | 
			
		||||
                key = (staid.networkCode, staid.stationCode)
 | 
			
		||||
                if not key in self.stationSetups:
 | 
			
		||||
                    self.stationSetups[key] = {}
 | 
			
		||||
                self.stationSetups[key][m] = params
 | 
			
		||||
            print(
 | 
			
		||||
                f"  + read {len(list(mod.bindings.keys()))} stations", file=sys.stderr
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def printUsage(self):
 | 
			
		||||
        print(
 | 
			
		||||
            """Usage:
 | 
			
		||||
  bindings2cfg [options]
 | 
			
		||||
 | 
			
		||||
Synchronize bindings from key files with processing system or output as
 | 
			
		||||
configuration XML file"""
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        seiscomp.client.Application.printUsage(self)
 | 
			
		||||
 | 
			
		||||
        print(
 | 
			
		||||
            """Examples:
 | 
			
		||||
Write bindings configuration from key directory to a configuration XML file:
 | 
			
		||||
  bindings2cfg --key-dir ./etc/key -o config.xml
 | 
			
		||||
 | 
			
		||||
Synchronize bindings configuration from key directory to a processing system
 | 
			
		||||
  bindings2cfg --key-dir ./etc/key -H proc
 | 
			
		||||
"""
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def send(self, *args):
 | 
			
		||||
        """
 | 
			
		||||
        A simple wrapper that sends a message and tries to resend it in case of
 | 
			
		||||
        an error.
 | 
			
		||||
        """
 | 
			
		||||
        while not self.connection().send(*args):
 | 
			
		||||
            print("Warning: sending failed, retrying", file=sys.stderr)
 | 
			
		||||
            time.sleep(1)
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        """
 | 
			
		||||
        Reimplements the main loop of the application. This methods collects
 | 
			
		||||
        all bindings and updates the database. It searches for already existing
 | 
			
		||||
        objects and updates them or creates new objects. Objects that is didn't
 | 
			
		||||
        touched are removed. This tool is the only one that should writes the
 | 
			
		||||
        configuration into the database and thus manages the content.
 | 
			
		||||
        """
 | 
			
		||||
        config = seiscomp.client.ConfigDB.Instance().config()
 | 
			
		||||
        if config is None:
 | 
			
		||||
            config = seiscomp.datamodel.Config()
 | 
			
		||||
 | 
			
		||||
        configMod = None
 | 
			
		||||
        obsoleteConfigMods = []
 | 
			
		||||
        moduleName = self._moduleName
 | 
			
		||||
 | 
			
		||||
        if self._outputFile is None or self._createNotifier == True:
 | 
			
		||||
            if not moduleName:
 | 
			
		||||
                moduleName = self.name()
 | 
			
		||||
            seiscomp.datamodel.Notifier.Enable()
 | 
			
		||||
        else:
 | 
			
		||||
            if not moduleName:
 | 
			
		||||
                moduleName = "trunk"
 | 
			
		||||
 | 
			
		||||
        configID = f"Config/{moduleName}"
 | 
			
		||||
 | 
			
		||||
        for i in range(config.configModuleCount()):
 | 
			
		||||
            if config.configModule(i).publicID() != configID:
 | 
			
		||||
                obsoleteConfigMods.append(config.configModule(i))
 | 
			
		||||
            else:
 | 
			
		||||
                configMod = config.configModule(i)
 | 
			
		||||
 | 
			
		||||
        # Remove obsolete config modules
 | 
			
		||||
        for cm in obsoleteConfigMods:
 | 
			
		||||
            print(f"- {cm.name()} / obsolete module configuration", file=sys.stderr)
 | 
			
		||||
            ps = seiscomp.datamodel.ParameterSet.Find(cm.parameterSetID())
 | 
			
		||||
            if not ps is None:
 | 
			
		||||
                ps.detach()
 | 
			
		||||
            cm.detach()
 | 
			
		||||
        del obsoleteConfigMods
 | 
			
		||||
 | 
			
		||||
        if not configMod:
 | 
			
		||||
            configMod = seiscomp.datamodel.ConfigModule.Find(configID)
 | 
			
		||||
            if configMod is None:
 | 
			
		||||
                configMod = seiscomp.datamodel.ConfigModule.Create(configID)
 | 
			
		||||
                config.add(configMod)
 | 
			
		||||
            else:
 | 
			
		||||
                if configMod.name() != moduleName:
 | 
			
		||||
                    configMod.update()
 | 
			
		||||
                if not configMod.enabled():
 | 
			
		||||
                    configMod.update()
 | 
			
		||||
 | 
			
		||||
            configMod.setName(moduleName)
 | 
			
		||||
            configMod.setEnabled(True)
 | 
			
		||||
        else:
 | 
			
		||||
            if configMod.name() != moduleName:
 | 
			
		||||
                configMod.setName(moduleName)
 | 
			
		||||
                configMod.update()
 | 
			
		||||
            paramSet = seiscomp.datamodel.ParameterSet.Find(configMod.parameterSetID())
 | 
			
		||||
            if configMod.parameterSetID():
 | 
			
		||||
                configMod.setParameterSetID("")
 | 
			
		||||
                configMod.update()
 | 
			
		||||
 | 
			
		||||
            if not paramSet is None:
 | 
			
		||||
                paramSet.detach()
 | 
			
		||||
 | 
			
		||||
        stationConfigs = {}
 | 
			
		||||
        obsoleteStationConfigs = []
 | 
			
		||||
 | 
			
		||||
        for i in range(configMod.configStationCount()):
 | 
			
		||||
            cs = configMod.configStation(i)
 | 
			
		||||
            if (cs.networkCode(), cs.stationCode()) in self.stationSetups:
 | 
			
		||||
                stationConfigs[(cs.networkCode(), cs.stationCode())] = cs
 | 
			
		||||
            else:
 | 
			
		||||
                obsoleteStationConfigs.append(cs)
 | 
			
		||||
 | 
			
		||||
        for cs in obsoleteStationConfigs:
 | 
			
		||||
            print(
 | 
			
		||||
                f"- {configMod.name()}/{cs.networkCode()}/{cs.stationCode()} / obsolete "
 | 
			
		||||
                "station configuration",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
            cs.detach()
 | 
			
		||||
        del obsoleteStationConfigs
 | 
			
		||||
 | 
			
		||||
        for staid, setups in list(self.stationSetups.items()):
 | 
			
		||||
            try:
 | 
			
		||||
                cs = stationConfigs[staid]
 | 
			
		||||
            except:
 | 
			
		||||
                cs = seiscomp.datamodel.ConfigStation.Find(
 | 
			
		||||
                    f"Config/{configMod.name()}/{staid[0]}/{staid[1]}"
 | 
			
		||||
                )
 | 
			
		||||
                if not cs:
 | 
			
		||||
                    cs = seiscomp.datamodel.ConfigStation.Create(
 | 
			
		||||
                        f"Config/{configMod.name()}/{staid[0]}/{staid[1]}"
 | 
			
		||||
                    )
 | 
			
		||||
                    configMod.add(cs)
 | 
			
		||||
                cs.setNetworkCode(staid[0])
 | 
			
		||||
                cs.setStationCode(staid[1])
 | 
			
		||||
                cs.setEnabled(True)
 | 
			
		||||
 | 
			
		||||
                ci = seiscomp.datamodel.CreationInfo()
 | 
			
		||||
                ci.setCreationTime(seiscomp.core.Time.GMT())
 | 
			
		||||
                ci.setAgencyID(self.agencyID())
 | 
			
		||||
                ci.setAuthor(self.name())
 | 
			
		||||
                cs.setCreationInfo(ci)
 | 
			
		||||
 | 
			
		||||
            stationSetups = {}
 | 
			
		||||
            obsoleteSetups = []
 | 
			
		||||
            for i in range(cs.setupCount()):
 | 
			
		||||
                setup = cs.setup(i)
 | 
			
		||||
                if setup.name() in setups:
 | 
			
		||||
                    stationSetups[setup.name()] = setup
 | 
			
		||||
                else:
 | 
			
		||||
                    obsoleteSetups.append(setup)
 | 
			
		||||
 | 
			
		||||
            for s in obsoleteSetups:
 | 
			
		||||
                print(
 | 
			
		||||
                    f"- {configMod.name()}/{cs.networkCode()}/{cs.stationCode()}/{setup.name()} "
 | 
			
		||||
                    "/ obsolete station setup",
 | 
			
		||||
                    file=sys.stderr,
 | 
			
		||||
                )
 | 
			
		||||
                ps = seiscomp.datamodel.ParameterSet.Find(s.parameterSetID())
 | 
			
		||||
                if ps:
 | 
			
		||||
                    ps.detach()
 | 
			
		||||
                s.detach()
 | 
			
		||||
            del obsoleteSetups
 | 
			
		||||
 | 
			
		||||
            newParamSets = {}
 | 
			
		||||
            globalSet = ""
 | 
			
		||||
            for mod, params in list(setups.items()):
 | 
			
		||||
                try:
 | 
			
		||||
                    setup = stationSetups[mod]
 | 
			
		||||
                except:
 | 
			
		||||
                    setup = seiscomp.datamodel.Setup()
 | 
			
		||||
                    setup.setName(mod)
 | 
			
		||||
                    setup.setEnabled(True)
 | 
			
		||||
                    cs.add(setup)
 | 
			
		||||
 | 
			
		||||
                paramSet = seiscomp.datamodel.ParameterSet.Find(setup.parameterSetID())
 | 
			
		||||
                if not paramSet:
 | 
			
		||||
                    paramSet = seiscomp.datamodel.ParameterSet.Find(
 | 
			
		||||
                        "ParameterSet/%s/Station/%s/%s/%s"
 | 
			
		||||
                        % (
 | 
			
		||||
                            configMod.name(),
 | 
			
		||||
                            cs.networkCode(),
 | 
			
		||||
                            cs.stationCode(),
 | 
			
		||||
                            setup.name(),
 | 
			
		||||
                        )
 | 
			
		||||
                    )
 | 
			
		||||
                    if not paramSet:
 | 
			
		||||
                        paramSet = seiscomp.datamodel.ParameterSet.Create(
 | 
			
		||||
                            "ParameterSet/%s/Station/%s/%s/%s"
 | 
			
		||||
                            % (
 | 
			
		||||
                                configMod.name(),
 | 
			
		||||
                                cs.networkCode(),
 | 
			
		||||
                                cs.stationCode(),
 | 
			
		||||
                                setup.name(),
 | 
			
		||||
                            )
 | 
			
		||||
                        )
 | 
			
		||||
                        config.add(paramSet)
 | 
			
		||||
                    paramSet.setModuleID(configMod.publicID())
 | 
			
		||||
                    paramSet.setCreated(seiscomp.core.Time.GMT())
 | 
			
		||||
                    newParamSets[paramSet.publicID()] = 1
 | 
			
		||||
                    setup.setParameterSetID(paramSet.publicID())
 | 
			
		||||
                    if mod in stationSetups:
 | 
			
		||||
                        setup.update()
 | 
			
		||||
                elif paramSet.moduleID() != configMod.publicID():
 | 
			
		||||
                    paramSet.setModuleID(configMod.publicID())
 | 
			
		||||
                    paramSet.update()
 | 
			
		||||
 | 
			
		||||
                # Synchronize existing parameterset with the new parameters
 | 
			
		||||
                sync(paramSet, params)
 | 
			
		||||
 | 
			
		||||
                if setup.name() == "default":
 | 
			
		||||
                    globalSet = paramSet.publicID()
 | 
			
		||||
 | 
			
		||||
            for i in range(cs.setupCount()):
 | 
			
		||||
                setup = cs.setup(i)
 | 
			
		||||
                paramSet = seiscomp.datamodel.ParameterSet.Find(setup.parameterSetID())
 | 
			
		||||
                if not paramSet:
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                if paramSet.publicID() != globalSet and paramSet.baseID() != globalSet:
 | 
			
		||||
                    paramSet.setBaseID(globalSet)
 | 
			
		||||
                    if not paramSet.publicID() in newParamSets:
 | 
			
		||||
                        paramSet.update()
 | 
			
		||||
 | 
			
		||||
        # Collect unused ParameterSets
 | 
			
		||||
        usedSets = {}
 | 
			
		||||
        for i in range(config.configModuleCount()):
 | 
			
		||||
            configMod = config.configModule(i)
 | 
			
		||||
            for j in range(configMod.configStationCount()):
 | 
			
		||||
                cs = configMod.configStation(j)
 | 
			
		||||
                for k in range(cs.setupCount()):
 | 
			
		||||
                    setup = cs.setup(k)
 | 
			
		||||
                    collect(usedSets, setup.parameterSetID())
 | 
			
		||||
 | 
			
		||||
        # Delete unused ParameterSets
 | 
			
		||||
        i = 0
 | 
			
		||||
        while i < config.parameterSetCount():
 | 
			
		||||
            paramSet = config.parameterSet(i)
 | 
			
		||||
            if not paramSet.publicID() in usedSets:
 | 
			
		||||
                print(
 | 
			
		||||
                    f"- {paramSet.publicID()} / obsolete parameter set", file=sys.stderr
 | 
			
		||||
                )
 | 
			
		||||
                paramSet.detach()
 | 
			
		||||
            else:
 | 
			
		||||
                i = i + 1
 | 
			
		||||
 | 
			
		||||
        # Generate output file and exit if configured
 | 
			
		||||
        if self._outputFile is not None:
 | 
			
		||||
            ar = seiscomp.io.XMLArchive()
 | 
			
		||||
            if not ar.create(self._outputFile):
 | 
			
		||||
                print(
 | 
			
		||||
                    f"Failed to created output file: {self._outputFile}",
 | 
			
		||||
                    file=sys.stderr,
 | 
			
		||||
                )
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
            ar.setFormattedOutput(True)
 | 
			
		||||
            if self._createNotifier:
 | 
			
		||||
                nmsg = seiscomp.datamodel.Notifier.GetMessage(True)
 | 
			
		||||
                ar.writeObject(nmsg)
 | 
			
		||||
            else:
 | 
			
		||||
                ar.writeObject(config)
 | 
			
		||||
            ar.close()
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        ncount = seiscomp.datamodel.Notifier.Size()
 | 
			
		||||
        if ncount > 0:
 | 
			
		||||
            print(f"+ synchronize {ncount} change(s)", file=sys.stderr)
 | 
			
		||||
        else:
 | 
			
		||||
            print("- database is already up-to-date", file=sys.stderr)
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        cfgmsg = seiscomp.datamodel.ConfigSyncMessage(False)
 | 
			
		||||
        cfgmsg.setCreationInfo(seiscomp.datamodel.CreationInfo())
 | 
			
		||||
        cfgmsg.creationInfo().setCreationTime(seiscomp.core.Time.GMT())
 | 
			
		||||
        cfgmsg.creationInfo().setAuthor(self.author())
 | 
			
		||||
        cfgmsg.creationInfo().setAgencyID(self.agencyID())
 | 
			
		||||
        self.send(seiscomp.client.Protocol.STATUS_GROUP, cfgmsg)
 | 
			
		||||
 | 
			
		||||
        # Send messages in a batch of 100 notifiers to not exceed the
 | 
			
		||||
        # maximum allowed message size of ~300kb.
 | 
			
		||||
        msg = seiscomp.datamodel.NotifierMessage()
 | 
			
		||||
        nmsg = seiscomp.datamodel.Notifier.GetMessage(False)
 | 
			
		||||
        count = 0
 | 
			
		||||
        sys.stderr.write("\r  + sending notifiers: %d%%" % (count * 100 / ncount))
 | 
			
		||||
        sys.stderr.flush()
 | 
			
		||||
        while nmsg:
 | 
			
		||||
            for o in nmsg:
 | 
			
		||||
                n = seiscomp.datamodel.Notifier.Cast(o)
 | 
			
		||||
                if n:
 | 
			
		||||
                    msg.attach(n)
 | 
			
		||||
 | 
			
		||||
            if msg.size() >= 100:
 | 
			
		||||
                count += msg.size()
 | 
			
		||||
                self.send("CONFIG", msg)
 | 
			
		||||
                msg.clear()
 | 
			
		||||
                sys.stderr.write(
 | 
			
		||||
                    "\r  + sending notifiers: %d%%" % (count * 100 / ncount)
 | 
			
		||||
                )
 | 
			
		||||
                sys.stderr.flush()
 | 
			
		||||
 | 
			
		||||
            nmsg = seiscomp.datamodel.Notifier.GetMessage(False)
 | 
			
		||||
 | 
			
		||||
        if msg.size() > 0:
 | 
			
		||||
            count += msg.size()
 | 
			
		||||
            self.send("CONFIG", msg)
 | 
			
		||||
            msg.clear()
 | 
			
		||||
            sys.stderr.write("\r  + sending notifiers: %d%%" % (count * 100 / ncount))
 | 
			
		||||
            sys.stderr.flush()
 | 
			
		||||
 | 
			
		||||
        sys.stderr.write("\n")
 | 
			
		||||
 | 
			
		||||
        # Notify about end of synchronization
 | 
			
		||||
        cfgmsg.creationInfo().setCreationTime(seiscomp.core.Time.GMT())
 | 
			
		||||
        cfgmsg.isFinished = True
 | 
			
		||||
        self.send(seiscomp.client.Protocol.STATUS_GROUP, cfgmsg)
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    app = ConfigDBUpdater(len(sys.argv), sys.argv)
 | 
			
		||||
    return app()
 | 
			
		||||
							
								
								
									
										1984
									
								
								lib/python/seiscomp/client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1984
									
								
								lib/python/seiscomp/client.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										857
									
								
								lib/python/seiscomp/config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										857
									
								
								lib/python/seiscomp/config.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,857 @@
 | 
			
		||||
# This file was automatically generated by SWIG (http://www.swig.org).
 | 
			
		||||
# Version 4.0.2
 | 
			
		||||
#
 | 
			
		||||
# Do not make changes to this file unless you know what you are doing--modify
 | 
			
		||||
# the SWIG interface file instead.
 | 
			
		||||
 | 
			
		||||
from sys import version_info as _swig_python_version_info
 | 
			
		||||
if _swig_python_version_info < (2, 7, 0):
 | 
			
		||||
    raise RuntimeError("Python 2.7 or later required")
 | 
			
		||||
 | 
			
		||||
# Import the low-level C/C++ module
 | 
			
		||||
if __package__ or "." in __name__:
 | 
			
		||||
    from . import _config
 | 
			
		||||
else:
 | 
			
		||||
    import _config
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import builtins as __builtin__
 | 
			
		||||
except ImportError:
 | 
			
		||||
    import __builtin__
 | 
			
		||||
 | 
			
		||||
def _swig_repr(self):
 | 
			
		||||
    try:
 | 
			
		||||
        strthis = "proxy of " + self.this.__repr__()
 | 
			
		||||
    except __builtin__.Exception:
 | 
			
		||||
        strthis = ""
 | 
			
		||||
    return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _swig_setattr_nondynamic_instance_variable(set):
 | 
			
		||||
    def set_instance_attr(self, name, value):
 | 
			
		||||
        if name == "thisown":
 | 
			
		||||
            self.this.own(value)
 | 
			
		||||
        elif name == "this":
 | 
			
		||||
            set(self, name, value)
 | 
			
		||||
        elif hasattr(self, name) and isinstance(getattr(type(self), name), property):
 | 
			
		||||
            set(self, name, value)
 | 
			
		||||
        else:
 | 
			
		||||
            raise AttributeError("You cannot add instance attributes to %s" % self)
 | 
			
		||||
    return set_instance_attr
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _swig_setattr_nondynamic_class_variable(set):
 | 
			
		||||
    def set_class_attr(cls, name, value):
 | 
			
		||||
        if hasattr(cls, name) and not isinstance(getattr(cls, name), property):
 | 
			
		||||
            set(cls, name, value)
 | 
			
		||||
        else:
 | 
			
		||||
            raise AttributeError("You cannot add class attributes to %s" % cls)
 | 
			
		||||
    return set_class_attr
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _swig_add_metaclass(metaclass):
 | 
			
		||||
    """Class decorator for adding a metaclass to a SWIG wrapped class - a slimmed down version of six.add_metaclass"""
 | 
			
		||||
    def wrapper(cls):
 | 
			
		||||
        return metaclass(cls.__name__, cls.__bases__, cls.__dict__.copy())
 | 
			
		||||
    return wrapper
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _SwigNonDynamicMeta(type):
 | 
			
		||||
    """Meta class to enforce nondynamic attributes (no new attributes) for a class"""
 | 
			
		||||
    __setattr__ = _swig_setattr_nondynamic_class_variable(type.__setattr__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import weakref
 | 
			
		||||
 | 
			
		||||
class SwigPyIterator(object):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        raise AttributeError("No constructor defined - class is abstract")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
    __swig_destroy__ = _config.delete_SwigPyIterator
 | 
			
		||||
 | 
			
		||||
    def value(self):
 | 
			
		||||
        return _config.SwigPyIterator_value(self)
 | 
			
		||||
 | 
			
		||||
    def incr(self, n=1):
 | 
			
		||||
        return _config.SwigPyIterator_incr(self, n)
 | 
			
		||||
 | 
			
		||||
    def decr(self, n=1):
 | 
			
		||||
        return _config.SwigPyIterator_decr(self, n)
 | 
			
		||||
 | 
			
		||||
    def distance(self, x):
 | 
			
		||||
        return _config.SwigPyIterator_distance(self, x)
 | 
			
		||||
 | 
			
		||||
    def equal(self, x):
 | 
			
		||||
        return _config.SwigPyIterator_equal(self, x)
 | 
			
		||||
 | 
			
		||||
    def copy(self):
 | 
			
		||||
        return _config.SwigPyIterator_copy(self)
 | 
			
		||||
 | 
			
		||||
    def next(self):
 | 
			
		||||
        return _config.SwigPyIterator_next(self)
 | 
			
		||||
 | 
			
		||||
    def __next__(self):
 | 
			
		||||
        return _config.SwigPyIterator___next__(self)
 | 
			
		||||
 | 
			
		||||
    def previous(self):
 | 
			
		||||
        return _config.SwigPyIterator_previous(self)
 | 
			
		||||
 | 
			
		||||
    def advance(self, n):
 | 
			
		||||
        return _config.SwigPyIterator_advance(self, n)
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, x):
 | 
			
		||||
        return _config.SwigPyIterator___eq__(self, x)
 | 
			
		||||
 | 
			
		||||
    def __ne__(self, x):
 | 
			
		||||
        return _config.SwigPyIterator___ne__(self, x)
 | 
			
		||||
 | 
			
		||||
    def __iadd__(self, n):
 | 
			
		||||
        return _config.SwigPyIterator___iadd__(self, n)
 | 
			
		||||
 | 
			
		||||
    def __isub__(self, n):
 | 
			
		||||
        return _config.SwigPyIterator___isub__(self, n)
 | 
			
		||||
 | 
			
		||||
    def __add__(self, n):
 | 
			
		||||
        return _config.SwigPyIterator___add__(self, n)
 | 
			
		||||
 | 
			
		||||
    def __sub__(self, *args):
 | 
			
		||||
        return _config.SwigPyIterator___sub__(self, *args)
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
# Register SwigPyIterator in _config:
 | 
			
		||||
_config.SwigPyIterator_swigregister(SwigPyIterator)
 | 
			
		||||
 | 
			
		||||
ERROR = _config.ERROR
 | 
			
		||||
WARNING = _config.WARNING
 | 
			
		||||
INFO = _config.INFO
 | 
			
		||||
DEBUG = _config.DEBUG
 | 
			
		||||
class Logger(object):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
    __swig_destroy__ = _config.delete_Logger
 | 
			
		||||
 | 
			
		||||
    def log(self, arg0, filename, line, msg):
 | 
			
		||||
        return _config.Logger_log(self, arg0, filename, line, msg)
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        if self.__class__ == Logger:
 | 
			
		||||
            _self = None
 | 
			
		||||
        else:
 | 
			
		||||
            _self = self
 | 
			
		||||
        _config.Logger_swiginit(self, _config.new_Logger(_self, ))
 | 
			
		||||
    def __disown__(self):
 | 
			
		||||
        self.this.disown()
 | 
			
		||||
        _config.disown_Logger(self)
 | 
			
		||||
        return weakref.proxy(self)
 | 
			
		||||
 | 
			
		||||
# Register Logger in _config:
 | 
			
		||||
_config.Logger_swigregister(Logger)
 | 
			
		||||
 | 
			
		||||
class Exception(object):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args):
 | 
			
		||||
        _config.Exception_swiginit(self, _config.new_Exception(*args))
 | 
			
		||||
    __swig_destroy__ = _config.delete_Exception
 | 
			
		||||
 | 
			
		||||
    def what(self):
 | 
			
		||||
        return _config.Exception_what(self)
 | 
			
		||||
 | 
			
		||||
# Register Exception in _config:
 | 
			
		||||
_config.Exception_swigregister(Exception)
 | 
			
		||||
cvar = _config.cvar
 | 
			
		||||
 | 
			
		||||
class OptionNotFoundException(Exception):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args):
 | 
			
		||||
        _config.OptionNotFoundException_swiginit(self, _config.new_OptionNotFoundException(*args))
 | 
			
		||||
    __swig_destroy__ = _config.delete_OptionNotFoundException
 | 
			
		||||
 | 
			
		||||
# Register OptionNotFoundException in _config:
 | 
			
		||||
_config.OptionNotFoundException_swigregister(OptionNotFoundException)
 | 
			
		||||
 | 
			
		||||
class TypeConversionException(Exception):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args):
 | 
			
		||||
        _config.TypeConversionException_swiginit(self, _config.new_TypeConversionException(*args))
 | 
			
		||||
    __swig_destroy__ = _config.delete_TypeConversionException
 | 
			
		||||
 | 
			
		||||
# Register TypeConversionException in _config:
 | 
			
		||||
_config.TypeConversionException_swigregister(TypeConversionException)
 | 
			
		||||
 | 
			
		||||
class SyntaxException(Exception):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args):
 | 
			
		||||
        _config.SyntaxException_swiginit(self, _config.new_SyntaxException(*args))
 | 
			
		||||
    __swig_destroy__ = _config.delete_SyntaxException
 | 
			
		||||
 | 
			
		||||
# Register SyntaxException in _config:
 | 
			
		||||
_config.SyntaxException_swigregister(SyntaxException)
 | 
			
		||||
 | 
			
		||||
class CaseSensitivityException(Exception):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args):
 | 
			
		||||
        _config.CaseSensitivityException_swiginit(self, _config.new_CaseSensitivityException(*args))
 | 
			
		||||
    __swig_destroy__ = _config.delete_CaseSensitivityException
 | 
			
		||||
 | 
			
		||||
# Register CaseSensitivityException in _config:
 | 
			
		||||
_config.CaseSensitivityException_swigregister(CaseSensitivityException)
 | 
			
		||||
 | 
			
		||||
class Symbol(object):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args):
 | 
			
		||||
        _config.Symbol_swiginit(self, _config.new_Symbol(*args))
 | 
			
		||||
 | 
			
		||||
    def set(self, name, ns, values, uri, comment, stage=-1):
 | 
			
		||||
        return _config.Symbol_set(self, name, ns, values, uri, comment, stage)
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, symbol):
 | 
			
		||||
        return _config.Symbol___eq__(self, symbol)
 | 
			
		||||
 | 
			
		||||
    def toString(self):
 | 
			
		||||
        return _config.Symbol_toString(self)
 | 
			
		||||
    name = property(_config.Symbol_name_get, _config.Symbol_name_set)
 | 
			
		||||
    ns = property(_config.Symbol_ns_get, _config.Symbol_ns_set)
 | 
			
		||||
    content = property(_config.Symbol_content_get, _config.Symbol_content_set)
 | 
			
		||||
    values = property(_config.Symbol_values_get, _config.Symbol_values_set)
 | 
			
		||||
    uri = property(_config.Symbol_uri_get, _config.Symbol_uri_set)
 | 
			
		||||
    comment = property(_config.Symbol_comment_get, _config.Symbol_comment_set)
 | 
			
		||||
    stage = property(_config.Symbol_stage_get, _config.Symbol_stage_set)
 | 
			
		||||
    line = property(_config.Symbol_line_get, _config.Symbol_line_set)
 | 
			
		||||
    __swig_destroy__ = _config.delete_Symbol
 | 
			
		||||
 | 
			
		||||
# Register Symbol in _config:
 | 
			
		||||
_config.Symbol_swigregister(Symbol)
 | 
			
		||||
 | 
			
		||||
class SymbolTable(object):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        _config.SymbolTable_swiginit(self, _config.new_SymbolTable())
 | 
			
		||||
 | 
			
		||||
    def setCaseSensitivityCheck(self, arg2):
 | 
			
		||||
        return _config.SymbolTable_setCaseSensitivityCheck(self, arg2)
 | 
			
		||||
 | 
			
		||||
    def setLogger(self, arg2):
 | 
			
		||||
        return _config.SymbolTable_setLogger(self, arg2)
 | 
			
		||||
 | 
			
		||||
    def logger(self):
 | 
			
		||||
        return _config.SymbolTable_logger(self)
 | 
			
		||||
 | 
			
		||||
    def add(self, *args):
 | 
			
		||||
        return _config.SymbolTable_add(self, *args)
 | 
			
		||||
 | 
			
		||||
    def get(self, *args):
 | 
			
		||||
        return _config.SymbolTable_get(self, *args)
 | 
			
		||||
 | 
			
		||||
    def remove(self, name):
 | 
			
		||||
        return _config.SymbolTable_remove(self, name)
 | 
			
		||||
 | 
			
		||||
    def incrementObjectCount(self):
 | 
			
		||||
        return _config.SymbolTable_incrementObjectCount(self)
 | 
			
		||||
 | 
			
		||||
    def decrementObjectCount(self):
 | 
			
		||||
        return _config.SymbolTable_decrementObjectCount(self)
 | 
			
		||||
 | 
			
		||||
    def objectCount(self):
 | 
			
		||||
        return _config.SymbolTable_objectCount(self)
 | 
			
		||||
 | 
			
		||||
    def toString(self):
 | 
			
		||||
        return _config.SymbolTable_toString(self)
 | 
			
		||||
 | 
			
		||||
    def hasFileBeenIncluded(self, fileName):
 | 
			
		||||
        return _config.SymbolTable_hasFileBeenIncluded(self, fileName)
 | 
			
		||||
 | 
			
		||||
    def addToIncludedFiles(self, fileName):
 | 
			
		||||
        return _config.SymbolTable_addToIncludedFiles(self, fileName)
 | 
			
		||||
 | 
			
		||||
    def includesBegin(self):
 | 
			
		||||
        return _config.SymbolTable_includesBegin(self)
 | 
			
		||||
 | 
			
		||||
    def includesEnd(self):
 | 
			
		||||
        return _config.SymbolTable_includesEnd(self)
 | 
			
		||||
 | 
			
		||||
    def begin(self):
 | 
			
		||||
        return _config.SymbolTable_begin(self)
 | 
			
		||||
 | 
			
		||||
    def end(self):
 | 
			
		||||
        return _config.SymbolTable_end(self)
 | 
			
		||||
    __swig_destroy__ = _config.delete_SymbolTable
 | 
			
		||||
 | 
			
		||||
# Register SymbolTable in _config:
 | 
			
		||||
_config.SymbolTable_swigregister(SymbolTable)
 | 
			
		||||
 | 
			
		||||
class Config(object):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        _config.Config_swiginit(self, _config.new_Config())
 | 
			
		||||
    __swig_destroy__ = _config.delete_Config
 | 
			
		||||
 | 
			
		||||
    def setCaseSensitivityCheck(self, arg2):
 | 
			
		||||
        return _config.Config_setCaseSensitivityCheck(self, arg2)
 | 
			
		||||
 | 
			
		||||
    def readConfig(self, file, stage=-1, raw=False):
 | 
			
		||||
        return _config.Config_readConfig(self, file, stage, raw)
 | 
			
		||||
 | 
			
		||||
    def writeConfig(self, *args):
 | 
			
		||||
        return _config.Config_writeConfig(self, *args)
 | 
			
		||||
 | 
			
		||||
    def setLogger(self, logger):
 | 
			
		||||
        return _config.Config_setLogger(self, logger)
 | 
			
		||||
 | 
			
		||||
    def symbolsToString(self):
 | 
			
		||||
        return _config.Config_symbolsToString(self)
 | 
			
		||||
 | 
			
		||||
    def names(self):
 | 
			
		||||
        return _config.Config_names(self)
 | 
			
		||||
 | 
			
		||||
    def visitedFilesToString(self):
 | 
			
		||||
        return _config.Config_visitedFilesToString(self)
 | 
			
		||||
 | 
			
		||||
    def getInt(self, *args):
 | 
			
		||||
        return _config.Config_getInt(self, *args)
 | 
			
		||||
 | 
			
		||||
    def setInt(self, name, value):
 | 
			
		||||
        return _config.Config_setInt(self, name, value)
 | 
			
		||||
 | 
			
		||||
    def getDouble(self, *args):
 | 
			
		||||
        return _config.Config_getDouble(self, *args)
 | 
			
		||||
 | 
			
		||||
    def setDouble(self, name, value):
 | 
			
		||||
        return _config.Config_setDouble(self, name, value)
 | 
			
		||||
 | 
			
		||||
    def getBool(self, *args):
 | 
			
		||||
        return _config.Config_getBool(self, *args)
 | 
			
		||||
 | 
			
		||||
    def setBool(self, name, value):
 | 
			
		||||
        return _config.Config_setBool(self, name, value)
 | 
			
		||||
 | 
			
		||||
    def getString(self, *args):
 | 
			
		||||
        return _config.Config_getString(self, *args)
 | 
			
		||||
 | 
			
		||||
    def setString(self, name, value):
 | 
			
		||||
        return _config.Config_setString(self, name, value)
 | 
			
		||||
 | 
			
		||||
    def remove(self, name):
 | 
			
		||||
        return _config.Config_remove(self, name)
 | 
			
		||||
 | 
			
		||||
    def getInts(self, *args):
 | 
			
		||||
        return _config.Config_getInts(self, *args)
 | 
			
		||||
 | 
			
		||||
    def setInts(self, name, values):
 | 
			
		||||
        return _config.Config_setInts(self, name, values)
 | 
			
		||||
 | 
			
		||||
    def getDoubles(self, *args):
 | 
			
		||||
        return _config.Config_getDoubles(self, *args)
 | 
			
		||||
 | 
			
		||||
    def setDoubles(self, name, values):
 | 
			
		||||
        return _config.Config_setDoubles(self, name, values)
 | 
			
		||||
 | 
			
		||||
    def getBools(self, *args):
 | 
			
		||||
        return _config.Config_getBools(self, *args)
 | 
			
		||||
 | 
			
		||||
    def setBools(self, name, values):
 | 
			
		||||
        return _config.Config_setBools(self, name, values)
 | 
			
		||||
 | 
			
		||||
    def getStrings(self, *args):
 | 
			
		||||
        return _config.Config_getStrings(self, *args)
 | 
			
		||||
 | 
			
		||||
    def setStrings(self, name, values):
 | 
			
		||||
        return _config.Config_setStrings(self, name, values)
 | 
			
		||||
 | 
			
		||||
    def symbolTable(self):
 | 
			
		||||
        return _config.Config_symbolTable(self)
 | 
			
		||||
 | 
			
		||||
    def eval(self, rvalue, result, resolveReferences=True, errmsg=None):
 | 
			
		||||
        return _config.Config_eval(self, rvalue, result, resolveReferences, errmsg)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def Eval(rvalue, result, resolveReferences=True, symtab=None, errmsg=None):
 | 
			
		||||
        return _config.Config_Eval(rvalue, result, resolveReferences, symtab, errmsg)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def writeValues(os, symbol, multilineLists=False):
 | 
			
		||||
        return _config.Config_writeValues(os, symbol, multilineLists)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def writeContent(os, symbol, multilineLists=False):
 | 
			
		||||
        return _config.Config_writeContent(os, symbol, multilineLists)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def writeSymbol(os, symbol, multilineLists=False):
 | 
			
		||||
        return _config.Config_writeSymbol(os, symbol, multilineLists)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def escapeIdentifier(arg1):
 | 
			
		||||
        return _config.Config_escapeIdentifier(arg1)
 | 
			
		||||
 | 
			
		||||
    def trackVariables(self, enabled):
 | 
			
		||||
        return _config.Config_trackVariables(self, enabled)
 | 
			
		||||
 | 
			
		||||
    def getVariables(self):
 | 
			
		||||
        return _config.Config_getVariables(self)
 | 
			
		||||
 | 
			
		||||
    def escape(self, arg2):
 | 
			
		||||
        return _config.Config_escape(self, arg2)
 | 
			
		||||
 | 
			
		||||
# Register Config in _config:
 | 
			
		||||
_config.Config_swigregister(Config)
 | 
			
		||||
 | 
			
		||||
def Config_Eval(rvalue, result, resolveReferences=True, symtab=None, errmsg=None):
 | 
			
		||||
    return _config.Config_Eval(rvalue, result, resolveReferences, symtab, errmsg)
 | 
			
		||||
 | 
			
		||||
def Config_writeValues(os, symbol, multilineLists=False):
 | 
			
		||||
    return _config.Config_writeValues(os, symbol, multilineLists)
 | 
			
		||||
 | 
			
		||||
def Config_writeContent(os, symbol, multilineLists=False):
 | 
			
		||||
    return _config.Config_writeContent(os, symbol, multilineLists)
 | 
			
		||||
 | 
			
		||||
def Config_writeSymbol(os, symbol, multilineLists=False):
 | 
			
		||||
    return _config.Config_writeSymbol(os, symbol, multilineLists)
 | 
			
		||||
 | 
			
		||||
def Config_escapeIdentifier(arg1):
 | 
			
		||||
    return _config.Config_escapeIdentifier(arg1)
 | 
			
		||||
 | 
			
		||||
class VectorStr(object):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
 | 
			
		||||
    def iterator(self):
 | 
			
		||||
        return _config.VectorStr_iterator(self)
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        return self.iterator()
 | 
			
		||||
 | 
			
		||||
    def __nonzero__(self):
 | 
			
		||||
        return _config.VectorStr___nonzero__(self)
 | 
			
		||||
 | 
			
		||||
    def __bool__(self):
 | 
			
		||||
        return _config.VectorStr___bool__(self)
 | 
			
		||||
 | 
			
		||||
    def __len__(self):
 | 
			
		||||
        return _config.VectorStr___len__(self)
 | 
			
		||||
 | 
			
		||||
    def __getslice__(self, i, j):
 | 
			
		||||
        return _config.VectorStr___getslice__(self, i, j)
 | 
			
		||||
 | 
			
		||||
    def __setslice__(self, *args):
 | 
			
		||||
        return _config.VectorStr___setslice__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __delslice__(self, i, j):
 | 
			
		||||
        return _config.VectorStr___delslice__(self, i, j)
 | 
			
		||||
 | 
			
		||||
    def __delitem__(self, *args):
 | 
			
		||||
        return _config.VectorStr___delitem__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __getitem__(self, *args):
 | 
			
		||||
        return _config.VectorStr___getitem__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __setitem__(self, *args):
 | 
			
		||||
        return _config.VectorStr___setitem__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def pop(self):
 | 
			
		||||
        return _config.VectorStr_pop(self)
 | 
			
		||||
 | 
			
		||||
    def append(self, x):
 | 
			
		||||
        return _config.VectorStr_append(self, x)
 | 
			
		||||
 | 
			
		||||
    def empty(self):
 | 
			
		||||
        return _config.VectorStr_empty(self)
 | 
			
		||||
 | 
			
		||||
    def size(self):
 | 
			
		||||
        return _config.VectorStr_size(self)
 | 
			
		||||
 | 
			
		||||
    def swap(self, v):
 | 
			
		||||
        return _config.VectorStr_swap(self, v)
 | 
			
		||||
 | 
			
		||||
    def begin(self):
 | 
			
		||||
        return _config.VectorStr_begin(self)
 | 
			
		||||
 | 
			
		||||
    def end(self):
 | 
			
		||||
        return _config.VectorStr_end(self)
 | 
			
		||||
 | 
			
		||||
    def rbegin(self):
 | 
			
		||||
        return _config.VectorStr_rbegin(self)
 | 
			
		||||
 | 
			
		||||
    def rend(self):
 | 
			
		||||
        return _config.VectorStr_rend(self)
 | 
			
		||||
 | 
			
		||||
    def clear(self):
 | 
			
		||||
        return _config.VectorStr_clear(self)
 | 
			
		||||
 | 
			
		||||
    def get_allocator(self):
 | 
			
		||||
        return _config.VectorStr_get_allocator(self)
 | 
			
		||||
 | 
			
		||||
    def pop_back(self):
 | 
			
		||||
        return _config.VectorStr_pop_back(self)
 | 
			
		||||
 | 
			
		||||
    def erase(self, *args):
 | 
			
		||||
        return _config.VectorStr_erase(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args):
 | 
			
		||||
        _config.VectorStr_swiginit(self, _config.new_VectorStr(*args))
 | 
			
		||||
 | 
			
		||||
    def push_back(self, x):
 | 
			
		||||
        return _config.VectorStr_push_back(self, x)
 | 
			
		||||
 | 
			
		||||
    def front(self):
 | 
			
		||||
        return _config.VectorStr_front(self)
 | 
			
		||||
 | 
			
		||||
    def back(self):
 | 
			
		||||
        return _config.VectorStr_back(self)
 | 
			
		||||
 | 
			
		||||
    def assign(self, n, x):
 | 
			
		||||
        return _config.VectorStr_assign(self, n, x)
 | 
			
		||||
 | 
			
		||||
    def resize(self, *args):
 | 
			
		||||
        return _config.VectorStr_resize(self, *args)
 | 
			
		||||
 | 
			
		||||
    def insert(self, *args):
 | 
			
		||||
        return _config.VectorStr_insert(self, *args)
 | 
			
		||||
 | 
			
		||||
    def reserve(self, n):
 | 
			
		||||
        return _config.VectorStr_reserve(self, n)
 | 
			
		||||
 | 
			
		||||
    def capacity(self):
 | 
			
		||||
        return _config.VectorStr_capacity(self)
 | 
			
		||||
    __swig_destroy__ = _config.delete_VectorStr
 | 
			
		||||
 | 
			
		||||
# Register VectorStr in _config:
 | 
			
		||||
_config.VectorStr_swigregister(VectorStr)
 | 
			
		||||
 | 
			
		||||
class VectorInt(object):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
 | 
			
		||||
    def iterator(self):
 | 
			
		||||
        return _config.VectorInt_iterator(self)
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        return self.iterator()
 | 
			
		||||
 | 
			
		||||
    def __nonzero__(self):
 | 
			
		||||
        return _config.VectorInt___nonzero__(self)
 | 
			
		||||
 | 
			
		||||
    def __bool__(self):
 | 
			
		||||
        return _config.VectorInt___bool__(self)
 | 
			
		||||
 | 
			
		||||
    def __len__(self):
 | 
			
		||||
        return _config.VectorInt___len__(self)
 | 
			
		||||
 | 
			
		||||
    def __getslice__(self, i, j):
 | 
			
		||||
        return _config.VectorInt___getslice__(self, i, j)
 | 
			
		||||
 | 
			
		||||
    def __setslice__(self, *args):
 | 
			
		||||
        return _config.VectorInt___setslice__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __delslice__(self, i, j):
 | 
			
		||||
        return _config.VectorInt___delslice__(self, i, j)
 | 
			
		||||
 | 
			
		||||
    def __delitem__(self, *args):
 | 
			
		||||
        return _config.VectorInt___delitem__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __getitem__(self, *args):
 | 
			
		||||
        return _config.VectorInt___getitem__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __setitem__(self, *args):
 | 
			
		||||
        return _config.VectorInt___setitem__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def pop(self):
 | 
			
		||||
        return _config.VectorInt_pop(self)
 | 
			
		||||
 | 
			
		||||
    def append(self, x):
 | 
			
		||||
        return _config.VectorInt_append(self, x)
 | 
			
		||||
 | 
			
		||||
    def empty(self):
 | 
			
		||||
        return _config.VectorInt_empty(self)
 | 
			
		||||
 | 
			
		||||
    def size(self):
 | 
			
		||||
        return _config.VectorInt_size(self)
 | 
			
		||||
 | 
			
		||||
    def swap(self, v):
 | 
			
		||||
        return _config.VectorInt_swap(self, v)
 | 
			
		||||
 | 
			
		||||
    def begin(self):
 | 
			
		||||
        return _config.VectorInt_begin(self)
 | 
			
		||||
 | 
			
		||||
    def end(self):
 | 
			
		||||
        return _config.VectorInt_end(self)
 | 
			
		||||
 | 
			
		||||
    def rbegin(self):
 | 
			
		||||
        return _config.VectorInt_rbegin(self)
 | 
			
		||||
 | 
			
		||||
    def rend(self):
 | 
			
		||||
        return _config.VectorInt_rend(self)
 | 
			
		||||
 | 
			
		||||
    def clear(self):
 | 
			
		||||
        return _config.VectorInt_clear(self)
 | 
			
		||||
 | 
			
		||||
    def get_allocator(self):
 | 
			
		||||
        return _config.VectorInt_get_allocator(self)
 | 
			
		||||
 | 
			
		||||
    def pop_back(self):
 | 
			
		||||
        return _config.VectorInt_pop_back(self)
 | 
			
		||||
 | 
			
		||||
    def erase(self, *args):
 | 
			
		||||
        return _config.VectorInt_erase(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args):
 | 
			
		||||
        _config.VectorInt_swiginit(self, _config.new_VectorInt(*args))
 | 
			
		||||
 | 
			
		||||
    def push_back(self, x):
 | 
			
		||||
        return _config.VectorInt_push_back(self, x)
 | 
			
		||||
 | 
			
		||||
    def front(self):
 | 
			
		||||
        return _config.VectorInt_front(self)
 | 
			
		||||
 | 
			
		||||
    def back(self):
 | 
			
		||||
        return _config.VectorInt_back(self)
 | 
			
		||||
 | 
			
		||||
    def assign(self, n, x):
 | 
			
		||||
        return _config.VectorInt_assign(self, n, x)
 | 
			
		||||
 | 
			
		||||
    def resize(self, *args):
 | 
			
		||||
        return _config.VectorInt_resize(self, *args)
 | 
			
		||||
 | 
			
		||||
    def insert(self, *args):
 | 
			
		||||
        return _config.VectorInt_insert(self, *args)
 | 
			
		||||
 | 
			
		||||
    def reserve(self, n):
 | 
			
		||||
        return _config.VectorInt_reserve(self, n)
 | 
			
		||||
 | 
			
		||||
    def capacity(self):
 | 
			
		||||
        return _config.VectorInt_capacity(self)
 | 
			
		||||
    __swig_destroy__ = _config.delete_VectorInt
 | 
			
		||||
 | 
			
		||||
# Register VectorInt in _config:
 | 
			
		||||
_config.VectorInt_swigregister(VectorInt)
 | 
			
		||||
 | 
			
		||||
class VectorDouble(object):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
 | 
			
		||||
    def iterator(self):
 | 
			
		||||
        return _config.VectorDouble_iterator(self)
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        return self.iterator()
 | 
			
		||||
 | 
			
		||||
    def __nonzero__(self):
 | 
			
		||||
        return _config.VectorDouble___nonzero__(self)
 | 
			
		||||
 | 
			
		||||
    def __bool__(self):
 | 
			
		||||
        return _config.VectorDouble___bool__(self)
 | 
			
		||||
 | 
			
		||||
    def __len__(self):
 | 
			
		||||
        return _config.VectorDouble___len__(self)
 | 
			
		||||
 | 
			
		||||
    def __getslice__(self, i, j):
 | 
			
		||||
        return _config.VectorDouble___getslice__(self, i, j)
 | 
			
		||||
 | 
			
		||||
    def __setslice__(self, *args):
 | 
			
		||||
        return _config.VectorDouble___setslice__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __delslice__(self, i, j):
 | 
			
		||||
        return _config.VectorDouble___delslice__(self, i, j)
 | 
			
		||||
 | 
			
		||||
    def __delitem__(self, *args):
 | 
			
		||||
        return _config.VectorDouble___delitem__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __getitem__(self, *args):
 | 
			
		||||
        return _config.VectorDouble___getitem__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __setitem__(self, *args):
 | 
			
		||||
        return _config.VectorDouble___setitem__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def pop(self):
 | 
			
		||||
        return _config.VectorDouble_pop(self)
 | 
			
		||||
 | 
			
		||||
    def append(self, x):
 | 
			
		||||
        return _config.VectorDouble_append(self, x)
 | 
			
		||||
 | 
			
		||||
    def empty(self):
 | 
			
		||||
        return _config.VectorDouble_empty(self)
 | 
			
		||||
 | 
			
		||||
    def size(self):
 | 
			
		||||
        return _config.VectorDouble_size(self)
 | 
			
		||||
 | 
			
		||||
    def swap(self, v):
 | 
			
		||||
        return _config.VectorDouble_swap(self, v)
 | 
			
		||||
 | 
			
		||||
    def begin(self):
 | 
			
		||||
        return _config.VectorDouble_begin(self)
 | 
			
		||||
 | 
			
		||||
    def end(self):
 | 
			
		||||
        return _config.VectorDouble_end(self)
 | 
			
		||||
 | 
			
		||||
    def rbegin(self):
 | 
			
		||||
        return _config.VectorDouble_rbegin(self)
 | 
			
		||||
 | 
			
		||||
    def rend(self):
 | 
			
		||||
        return _config.VectorDouble_rend(self)
 | 
			
		||||
 | 
			
		||||
    def clear(self):
 | 
			
		||||
        return _config.VectorDouble_clear(self)
 | 
			
		||||
 | 
			
		||||
    def get_allocator(self):
 | 
			
		||||
        return _config.VectorDouble_get_allocator(self)
 | 
			
		||||
 | 
			
		||||
    def pop_back(self):
 | 
			
		||||
        return _config.VectorDouble_pop_back(self)
 | 
			
		||||
 | 
			
		||||
    def erase(self, *args):
 | 
			
		||||
        return _config.VectorDouble_erase(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args):
 | 
			
		||||
        _config.VectorDouble_swiginit(self, _config.new_VectorDouble(*args))
 | 
			
		||||
 | 
			
		||||
    def push_back(self, x):
 | 
			
		||||
        return _config.VectorDouble_push_back(self, x)
 | 
			
		||||
 | 
			
		||||
    def front(self):
 | 
			
		||||
        return _config.VectorDouble_front(self)
 | 
			
		||||
 | 
			
		||||
    def back(self):
 | 
			
		||||
        return _config.VectorDouble_back(self)
 | 
			
		||||
 | 
			
		||||
    def assign(self, n, x):
 | 
			
		||||
        return _config.VectorDouble_assign(self, n, x)
 | 
			
		||||
 | 
			
		||||
    def resize(self, *args):
 | 
			
		||||
        return _config.VectorDouble_resize(self, *args)
 | 
			
		||||
 | 
			
		||||
    def insert(self, *args):
 | 
			
		||||
        return _config.VectorDouble_insert(self, *args)
 | 
			
		||||
 | 
			
		||||
    def reserve(self, n):
 | 
			
		||||
        return _config.VectorDouble_reserve(self, n)
 | 
			
		||||
 | 
			
		||||
    def capacity(self):
 | 
			
		||||
        return _config.VectorDouble_capacity(self)
 | 
			
		||||
    __swig_destroy__ = _config.delete_VectorDouble
 | 
			
		||||
 | 
			
		||||
# Register VectorDouble in _config:
 | 
			
		||||
_config.VectorDouble_swigregister(VectorDouble)
 | 
			
		||||
 | 
			
		||||
class VectorBool(object):
 | 
			
		||||
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
 | 
			
		||||
    __repr__ = _swig_repr
 | 
			
		||||
 | 
			
		||||
    def iterator(self):
 | 
			
		||||
        return _config.VectorBool_iterator(self)
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        return self.iterator()
 | 
			
		||||
 | 
			
		||||
    def __nonzero__(self):
 | 
			
		||||
        return _config.VectorBool___nonzero__(self)
 | 
			
		||||
 | 
			
		||||
    def __bool__(self):
 | 
			
		||||
        return _config.VectorBool___bool__(self)
 | 
			
		||||
 | 
			
		||||
    def __len__(self):
 | 
			
		||||
        return _config.VectorBool___len__(self)
 | 
			
		||||
 | 
			
		||||
    def __getslice__(self, i, j):
 | 
			
		||||
        return _config.VectorBool___getslice__(self, i, j)
 | 
			
		||||
 | 
			
		||||
    def __setslice__(self, *args):
 | 
			
		||||
        return _config.VectorBool___setslice__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __delslice__(self, i, j):
 | 
			
		||||
        return _config.VectorBool___delslice__(self, i, j)
 | 
			
		||||
 | 
			
		||||
    def __delitem__(self, *args):
 | 
			
		||||
        return _config.VectorBool___delitem__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __getitem__(self, *args):
 | 
			
		||||
        return _config.VectorBool___getitem__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __setitem__(self, *args):
 | 
			
		||||
        return _config.VectorBool___setitem__(self, *args)
 | 
			
		||||
 | 
			
		||||
    def pop(self):
 | 
			
		||||
        return _config.VectorBool_pop(self)
 | 
			
		||||
 | 
			
		||||
    def append(self, x):
 | 
			
		||||
        return _config.VectorBool_append(self, x)
 | 
			
		||||
 | 
			
		||||
    def empty(self):
 | 
			
		||||
        return _config.VectorBool_empty(self)
 | 
			
		||||
 | 
			
		||||
    def size(self):
 | 
			
		||||
        return _config.VectorBool_size(self)
 | 
			
		||||
 | 
			
		||||
    def swap(self, v):
 | 
			
		||||
        return _config.VectorBool_swap(self, v)
 | 
			
		||||
 | 
			
		||||
    def begin(self):
 | 
			
		||||
        return _config.VectorBool_begin(self)
 | 
			
		||||
 | 
			
		||||
    def end(self):
 | 
			
		||||
        return _config.VectorBool_end(self)
 | 
			
		||||
 | 
			
		||||
    def rbegin(self):
 | 
			
		||||
        return _config.VectorBool_rbegin(self)
 | 
			
		||||
 | 
			
		||||
    def rend(self):
 | 
			
		||||
        return _config.VectorBool_rend(self)
 | 
			
		||||
 | 
			
		||||
    def clear(self):
 | 
			
		||||
        return _config.VectorBool_clear(self)
 | 
			
		||||
 | 
			
		||||
    def get_allocator(self):
 | 
			
		||||
        return _config.VectorBool_get_allocator(self)
 | 
			
		||||
 | 
			
		||||
    def pop_back(self):
 | 
			
		||||
        return _config.VectorBool_pop_back(self)
 | 
			
		||||
 | 
			
		||||
    def erase(self, *args):
 | 
			
		||||
        return _config.VectorBool_erase(self, *args)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args):
 | 
			
		||||
        _config.VectorBool_swiginit(self, _config.new_VectorBool(*args))
 | 
			
		||||
 | 
			
		||||
    def push_back(self, x):
 | 
			
		||||
        return _config.VectorBool_push_back(self, x)
 | 
			
		||||
 | 
			
		||||
    def front(self):
 | 
			
		||||
        return _config.VectorBool_front(self)
 | 
			
		||||
 | 
			
		||||
    def back(self):
 | 
			
		||||
        return _config.VectorBool_back(self)
 | 
			
		||||
 | 
			
		||||
    def assign(self, n, x):
 | 
			
		||||
        return _config.VectorBool_assign(self, n, x)
 | 
			
		||||
 | 
			
		||||
    def resize(self, *args):
 | 
			
		||||
        return _config.VectorBool_resize(self, *args)
 | 
			
		||||
 | 
			
		||||
    def insert(self, *args):
 | 
			
		||||
        return _config.VectorBool_insert(self, *args)
 | 
			
		||||
 | 
			
		||||
    def reserve(self, n):
 | 
			
		||||
        return _config.VectorBool_reserve(self, n)
 | 
			
		||||
 | 
			
		||||
    def capacity(self):
 | 
			
		||||
        return _config.VectorBool_capacity(self)
 | 
			
		||||
    __swig_destroy__ = _config.delete_VectorBool
 | 
			
		||||
 | 
			
		||||
# Register VectorBool in _config:
 | 
			
		||||
_config.VectorBool_swigregister(VectorBool)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2769
									
								
								lib/python/seiscomp/core.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2769
									
								
								lib/python/seiscomp/core.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										23447
									
								
								lib/python/seiscomp/datamodel/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23447
									
								
								lib/python/seiscomp/datamodel/__init__.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								lib/python/seiscomp/datamodel/_datamodel.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lib/python/seiscomp/datamodel/_datamodel.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										0
									
								
								lib/python/seiscomp/fdsnws/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								lib/python/seiscomp/fdsnws/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										85
									
								
								lib/python/seiscomp/fdsnws/authresource.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								lib/python/seiscomp/fdsnws/authresource.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,85 @@
 | 
			
		||||
################################################################################
 | 
			
		||||
# Copyright (C) 2013-2014 by gempa GmbH
 | 
			
		||||
#
 | 
			
		||||
# HTTP -- Utility methods which generate HTTP result strings
 | 
			
		||||
#
 | 
			
		||||
# Author:  Stephan Herrnkind
 | 
			
		||||
# Email:   herrnkind@gempa.de
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
import base64
 | 
			
		||||
import datetime
 | 
			
		||||
import hashlib
 | 
			
		||||
import json
 | 
			
		||||
import time
 | 
			
		||||
import dateutil.parser
 | 
			
		||||
 | 
			
		||||
from twisted.web import http
 | 
			
		||||
 | 
			
		||||
import gnupg
 | 
			
		||||
 | 
			
		||||
import seiscomp.logging
 | 
			
		||||
 | 
			
		||||
from .utils import accessLog, u_str
 | 
			
		||||
 | 
			
		||||
from .http import BaseResource
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
class AuthResource(BaseResource):
 | 
			
		||||
    isLeaf = True
 | 
			
		||||
 | 
			
		||||
    def __init__(self, version, gnupghome, userdb):
 | 
			
		||||
        super().__init__(version)
 | 
			
		||||
 | 
			
		||||
        self.__gpg = gnupg.GPG(gnupghome=gnupghome)
 | 
			
		||||
        self.__userdb = userdb
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def render_POST(self, request):
 | 
			
		||||
        request.setHeader("Content-Type", "text/plain; charset=utf-8")
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            verified = self.__gpg.decrypt(request.content.getvalue())
 | 
			
		||||
 | 
			
		||||
        except OSError as e:
 | 
			
		||||
            msg = "gpg decrypt error"
 | 
			
		||||
            seiscomp.logging.warning(f"{msg}: {e}")
 | 
			
		||||
            return self.renderErrorPage(request, http.INTERNAL_SERVER_ERROR, msg)
 | 
			
		||||
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            msg = "invalid token"
 | 
			
		||||
            seiscomp.logging.warning(f"{msg}: {e}")
 | 
			
		||||
            return self.renderErrorPage(request, http.BAD_REQUEST, msg)
 | 
			
		||||
 | 
			
		||||
        if verified.trust_level is None or verified.trust_level < verified.TRUST_FULLY:
 | 
			
		||||
            msg = "token has invalid signature"
 | 
			
		||||
            seiscomp.logging.warning(msg)
 | 
			
		||||
            return self.renderErrorPage(request, http.BAD_REQUEST, msg)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            attributes = json.loads(u_str(verified.data))
 | 
			
		||||
            td = dateutil.parser.parse(
 | 
			
		||||
                attributes["valid_until"]
 | 
			
		||||
            ) - datetime.datetime.now(dateutil.tz.tzutc())
 | 
			
		||||
            lifetime = td.seconds + td.days * 24 * 3600
 | 
			
		||||
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            msg = "token has invalid validity"
 | 
			
		||||
            seiscomp.logging.warning(f"{msg}: {e}")
 | 
			
		||||
            return self.renderErrorPage(request, http.BAD_REQUEST, msg)
 | 
			
		||||
 | 
			
		||||
        if lifetime <= 0:
 | 
			
		||||
            msg = "token is expired"
 | 
			
		||||
            seiscomp.logging.warning(msg)
 | 
			
		||||
            return self.renderErrorPage(request, http.BAD_REQUEST, msg)
 | 
			
		||||
 | 
			
		||||
        userid = base64.urlsafe_b64encode(hashlib.sha256(verified.data).digest()[:18])
 | 
			
		||||
        password = self.__userdb.addUser(
 | 
			
		||||
            u_str(userid),
 | 
			
		||||
            attributes,
 | 
			
		||||
            time.time() + min(lifetime, 24 * 3600),
 | 
			
		||||
            u_str(verified.data),
 | 
			
		||||
        )
 | 
			
		||||
        accessLog(request, None, http.OK, len(userid) + len(password) + 1, None)
 | 
			
		||||
        return userid + b":" + password
 | 
			
		||||
							
								
								
									
										1442
									
								
								lib/python/seiscomp/fdsnws/availability.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1442
									
								
								lib/python/seiscomp/fdsnws/availability.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										796
									
								
								lib/python/seiscomp/fdsnws/dataselect.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										796
									
								
								lib/python/seiscomp/fdsnws/dataselect.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,796 @@
 | 
			
		||||
################################################################################
 | 
			
		||||
# Copyright (C) 2013-2014 by gempa GmbH
 | 
			
		||||
#
 | 
			
		||||
# FDSNDataSelect -- Implements the fdsnws-dataselect Web service, see
 | 
			
		||||
#   http://www.fdsn.org/webservices/
 | 
			
		||||
#
 | 
			
		||||
# Feature notes:
 | 
			
		||||
#   - 'quality' request parameter not implemented (information not available in
 | 
			
		||||
#     SeisComP)
 | 
			
		||||
#   - 'minimumlength' parameter is not implemented
 | 
			
		||||
#   - 'longestonly' parameter is not implemented
 | 
			
		||||
#
 | 
			
		||||
# Author:  Stephan Herrnkind
 | 
			
		||||
# Email:   herrnkind@gempa.de
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
from io import BytesIO
 | 
			
		||||
 | 
			
		||||
import dateutil.parser
 | 
			
		||||
 | 
			
		||||
from twisted.cred import portal
 | 
			
		||||
from twisted.web import http, resource, server
 | 
			
		||||
from twisted.internet import interfaces, reactor
 | 
			
		||||
 | 
			
		||||
from zope.interface import implementer
 | 
			
		||||
 | 
			
		||||
from seiscomp import logging, mseedlite
 | 
			
		||||
 | 
			
		||||
from seiscomp.client import Application
 | 
			
		||||
from seiscomp.core import Array, Record, Time
 | 
			
		||||
from seiscomp.io import RecordInput, RecordStream
 | 
			
		||||
 | 
			
		||||
from .http import HTTP, BaseResource
 | 
			
		||||
from .request import RequestOptions
 | 
			
		||||
from . import utils
 | 
			
		||||
 | 
			
		||||
from .reqtrack import RequestTrackerDB
 | 
			
		||||
from .fastsds import SDS
 | 
			
		||||
 | 
			
		||||
VERSION = "1.1.3"
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _DataSelectRequestOptions(RequestOptions):
 | 
			
		||||
    MinTime = Time(0, 1)
 | 
			
		||||
 | 
			
		||||
    PQuality = ["quality"]
 | 
			
		||||
    PMinimumLength = ["minimumlength"]
 | 
			
		||||
    PLongestOnly = ["longestonly"]
 | 
			
		||||
 | 
			
		||||
    QualityValues = ["B", "D", "M", "Q", "R"]
 | 
			
		||||
    OutputFormats = ["miniseed", "mseed"]
 | 
			
		||||
 | 
			
		||||
    POSTParams = RequestOptions.POSTParams + PQuality + PMinimumLength + PLongestOnly
 | 
			
		||||
    GETParams = RequestOptions.GETParams + POSTParams
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
 | 
			
		||||
        self.service = "fdsnws-dataselect"
 | 
			
		||||
 | 
			
		||||
        self.quality = self.QualityValues[0]
 | 
			
		||||
        self.minimumLength = None
 | 
			
		||||
        self.longestOnly = None
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def _checkTimes(self, realtimeGap):
 | 
			
		||||
        maxEndTime = Time(self.accessTime)
 | 
			
		||||
        if realtimeGap is not None:
 | 
			
		||||
            maxEndTime -= Time(realtimeGap, 0)
 | 
			
		||||
 | 
			
		||||
        for ro in self.streams:
 | 
			
		||||
            # create time if non was specified
 | 
			
		||||
            if ro.time is None:
 | 
			
		||||
                ro.time = RequestOptions.Time()
 | 
			
		||||
            # restrict time to 1970 - now
 | 
			
		||||
            if ro.time.start is None or ro.time.start < self.MinTime:
 | 
			
		||||
                ro.time.start = self.MinTime
 | 
			
		||||
            if ro.time.end is None or ro.time.end > maxEndTime:
 | 
			
		||||
                ro.time.end = maxEndTime
 | 
			
		||||
 | 
			
		||||
        # remove items with start time >= end time
 | 
			
		||||
        self.streams = [x for x in self.streams if x.time.start < x.time.end]
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def parse(self):
 | 
			
		||||
        # quality (optional), currently not supported
 | 
			
		||||
        key, value = self.getFirstValue(self.PQuality)
 | 
			
		||||
        if value is not None:
 | 
			
		||||
            value = value.upper()
 | 
			
		||||
            if value in self.QualityValues:
 | 
			
		||||
                self.quality = value
 | 
			
		||||
            else:
 | 
			
		||||
                self.raiseValueError(key)
 | 
			
		||||
 | 
			
		||||
        # minimumlength(optional), currently not supported
 | 
			
		||||
        self.minimumLength = self.parseFloat(self.PMinimumLength, 0)
 | 
			
		||||
 | 
			
		||||
        # longestonly (optional), currently not supported
 | 
			
		||||
        self.longestOnly = self.parseBool(self.PLongestOnly)
 | 
			
		||||
 | 
			
		||||
        # generic parameters
 | 
			
		||||
        self.parseTime()
 | 
			
		||||
        self.parseChannel()
 | 
			
		||||
        self.parseOutput()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
class _MyRecordStream:
 | 
			
		||||
    def __init__(self, url, trackerList, bufferSize):
 | 
			
		||||
        self.__url = url
 | 
			
		||||
        self.__trackerList = trackerList
 | 
			
		||||
        self.__bufferSize = bufferSize
 | 
			
		||||
        self.__tw = []
 | 
			
		||||
 | 
			
		||||
    def addStream(self, net, sta, loc, cha, startt, endt, restricted, archNet):
 | 
			
		||||
        self.__tw.append((net, sta, loc, cha, startt, endt, restricted, archNet))
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def __override_network(data, net):
 | 
			
		||||
        inp = BytesIO(data)
 | 
			
		||||
        out = BytesIO()
 | 
			
		||||
 | 
			
		||||
        for rec in mseedlite.Input(inp):
 | 
			
		||||
            rec.net = net
 | 
			
		||||
            rec_len_exp = 9
 | 
			
		||||
 | 
			
		||||
            while (1 << rec_len_exp) < rec.size:
 | 
			
		||||
                rec_len_exp += 1
 | 
			
		||||
 | 
			
		||||
            rec.write(out, rec_len_exp)
 | 
			
		||||
 | 
			
		||||
        return out.getvalue()
 | 
			
		||||
 | 
			
		||||
    def input(self):
 | 
			
		||||
        fastsdsPrefix = "fastsds://"
 | 
			
		||||
 | 
			
		||||
        if self.__url.startswith(fastsdsPrefix):
 | 
			
		||||
            fastsds = SDS(self.__url[len(fastsdsPrefix) :])
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            fastsds = None
 | 
			
		||||
 | 
			
		||||
        for net, sta, loc, cha, startt, endt, restricted, archNet in self.__tw:
 | 
			
		||||
            if not archNet:
 | 
			
		||||
                archNet = net
 | 
			
		||||
 | 
			
		||||
            size = 0
 | 
			
		||||
 | 
			
		||||
            if fastsds:
 | 
			
		||||
                start = dateutil.parser.parse(startt.iso()).replace(tzinfo=None)
 | 
			
		||||
                end = dateutil.parser.parse(endt.iso()).replace(tzinfo=None)
 | 
			
		||||
 | 
			
		||||
                for data in fastsds.getRawBytes(
 | 
			
		||||
                    start, end, archNet, sta, loc, cha, self.__bufferSize
 | 
			
		||||
                ):
 | 
			
		||||
                    size += len(data)
 | 
			
		||||
 | 
			
		||||
                    if archNet == net:
 | 
			
		||||
                        yield data
 | 
			
		||||
 | 
			
		||||
                    else:
 | 
			
		||||
                        try:
 | 
			
		||||
                            yield self.__override_network(data, net)
 | 
			
		||||
 | 
			
		||||
                        except Exception as e:
 | 
			
		||||
                            logging.error(f"could not override network code: {e}")
 | 
			
		||||
 | 
			
		||||
            else:
 | 
			
		||||
                rs = RecordStream.Open(self.__url)
 | 
			
		||||
 | 
			
		||||
                if rs is None:
 | 
			
		||||
                    logging.error("could not open record stream")
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
                rs.addStream(archNet, sta, loc, cha, startt, endt)
 | 
			
		||||
                rsInput = RecordInput(rs, Array.INT, Record.SAVE_RAW)
 | 
			
		||||
                eof = False
 | 
			
		||||
 | 
			
		||||
                while not eof:
 | 
			
		||||
                    data = b""
 | 
			
		||||
 | 
			
		||||
                    while len(data) < self.__bufferSize:
 | 
			
		||||
                        try:
 | 
			
		||||
                            rec = rsInput.next()
 | 
			
		||||
 | 
			
		||||
                        except Exception as e:
 | 
			
		||||
                            logging.error(str(e))
 | 
			
		||||
                            eof = True
 | 
			
		||||
                            break
 | 
			
		||||
 | 
			
		||||
                        if rec is None:
 | 
			
		||||
                            eof = True
 | 
			
		||||
                            break
 | 
			
		||||
 | 
			
		||||
                        data += rec.raw().str()
 | 
			
		||||
 | 
			
		||||
                    if data:
 | 
			
		||||
                        size += len(data)
 | 
			
		||||
 | 
			
		||||
                        if archNet == net:
 | 
			
		||||
                            yield data
 | 
			
		||||
 | 
			
		||||
                        else:
 | 
			
		||||
                            try:
 | 
			
		||||
                                yield self.__override_network(data, net)
 | 
			
		||||
 | 
			
		||||
                            except Exception as e:
 | 
			
		||||
                                logging.error(f"could not override network code: {e}")
 | 
			
		||||
 | 
			
		||||
            for tracker in self.__trackerList:
 | 
			
		||||
                net_class = "t" if net[0] in "0123456789XYZ" else "p"
 | 
			
		||||
 | 
			
		||||
                if size == 0:
 | 
			
		||||
                    tracker.line_status(
 | 
			
		||||
                        startt,
 | 
			
		||||
                        endt,
 | 
			
		||||
                        net,
 | 
			
		||||
                        sta,
 | 
			
		||||
                        cha,
 | 
			
		||||
                        loc,
 | 
			
		||||
                        restricted,
 | 
			
		||||
                        net_class,
 | 
			
		||||
                        True,
 | 
			
		||||
                        [],
 | 
			
		||||
                        "fdsnws",
 | 
			
		||||
                        "NODATA",
 | 
			
		||||
                        0,
 | 
			
		||||
                        "",
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                else:
 | 
			
		||||
                    tracker.line_status(
 | 
			
		||||
                        startt,
 | 
			
		||||
                        endt,
 | 
			
		||||
                        net,
 | 
			
		||||
                        sta,
 | 
			
		||||
                        cha,
 | 
			
		||||
                        loc,
 | 
			
		||||
                        restricted,
 | 
			
		||||
                        net_class,
 | 
			
		||||
                        True,
 | 
			
		||||
                        [],
 | 
			
		||||
                        "fdsnws",
 | 
			
		||||
                        "OK",
 | 
			
		||||
                        size,
 | 
			
		||||
                        "",
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
@implementer(interfaces.IPushProducer)
 | 
			
		||||
class _WaveformProducer:
 | 
			
		||||
    def __init__(self, req, ro, rs, fileName, trackerList):
 | 
			
		||||
        self.req = req
 | 
			
		||||
        self.ro = ro
 | 
			
		||||
        self.it = rs.input()
 | 
			
		||||
 | 
			
		||||
        self.fileName = fileName
 | 
			
		||||
        self.written = 0
 | 
			
		||||
 | 
			
		||||
        self.trackerList = trackerList
 | 
			
		||||
        self.paused = False
 | 
			
		||||
        self.stopped = False
 | 
			
		||||
        self.running = False
 | 
			
		||||
 | 
			
		||||
    def _flush(self, data):
 | 
			
		||||
        if self.stopped:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if not self.paused:
 | 
			
		||||
            reactor.callInThread(self._collectData)
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            self.running = False
 | 
			
		||||
 | 
			
		||||
        if self.written == 0:
 | 
			
		||||
            self.req.setHeader("Content-Type", "application/vnd.fdsn.mseed")
 | 
			
		||||
            self.req.setHeader(
 | 
			
		||||
                "Content-Disposition", f"attachment; filename={self.fileName}"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        self.req.write(data)
 | 
			
		||||
        self.written += len(data)
 | 
			
		||||
 | 
			
		||||
    def _finish(self):
 | 
			
		||||
        if self.stopped:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if self.written == 0:
 | 
			
		||||
            msg = "no waveform data found"
 | 
			
		||||
            errorpage = HTTP.renderErrorPage(
 | 
			
		||||
                self.req, http.NO_CONTENT, msg, VERSION, self.ro
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            if errorpage:
 | 
			
		||||
                self.req.write(errorpage)
 | 
			
		||||
 | 
			
		||||
            for tracker in self.trackerList:
 | 
			
		||||
                tracker.volume_status("fdsnws", "NODATA", 0, "")
 | 
			
		||||
                tracker.request_status("END", "")
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            logging.debug(
 | 
			
		||||
                f"{self.ro.service}: returned {self.written} bytes of mseed data"
 | 
			
		||||
            )
 | 
			
		||||
            utils.accessLog(self.req, self.ro, http.OK, self.written, None)
 | 
			
		||||
 | 
			
		||||
            for tracker in self.trackerList:
 | 
			
		||||
                tracker.volume_status("fdsnws", "OK", self.written, "")
 | 
			
		||||
                tracker.request_status("END", "")
 | 
			
		||||
 | 
			
		||||
        self.req.unregisterProducer()
 | 
			
		||||
        self.req.finish()
 | 
			
		||||
 | 
			
		||||
    def _collectData(self):
 | 
			
		||||
        try:
 | 
			
		||||
            reactor.callFromThread(self._flush, next(self.it))
 | 
			
		||||
 | 
			
		||||
        except StopIteration:
 | 
			
		||||
            reactor.callFromThread(self._finish)
 | 
			
		||||
 | 
			
		||||
    def pauseProducing(self):
 | 
			
		||||
        self.paused = True
 | 
			
		||||
 | 
			
		||||
    def resumeProducing(self):
 | 
			
		||||
        self.paused = False
 | 
			
		||||
 | 
			
		||||
        if not self.running:
 | 
			
		||||
            self.running = True
 | 
			
		||||
            reactor.callInThread(self._collectData)
 | 
			
		||||
 | 
			
		||||
    def stopProducing(self):
 | 
			
		||||
        self.stopped = True
 | 
			
		||||
 | 
			
		||||
        logging.debug(
 | 
			
		||||
            f"{self.ro.service}: returned {self.written} bytes of mseed data (not "
 | 
			
		||||
            "completed)"
 | 
			
		||||
        )
 | 
			
		||||
        utils.accessLog(self.req, self.ro, http.OK, self.written, "not completed")
 | 
			
		||||
 | 
			
		||||
        for tracker in self.trackerList:
 | 
			
		||||
            tracker.volume_status("fdsnws", "ERROR", self.written, "")
 | 
			
		||||
            tracker.request_status("END", "")
 | 
			
		||||
 | 
			
		||||
        self.req.unregisterProducer()
 | 
			
		||||
        self.req.finish()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
@implementer(portal.IRealm)
 | 
			
		||||
class FDSNDataSelectRealm:
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def __init__(self, inv, bufferSize, access):
 | 
			
		||||
        self.__inv = inv
 | 
			
		||||
        self.__bufferSize = bufferSize
 | 
			
		||||
        self.__access = access
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def requestAvatar(self, avatarId, _mind, *interfaces_):
 | 
			
		||||
        if resource.IResource in interfaces_:
 | 
			
		||||
            return (
 | 
			
		||||
                resource.IResource,
 | 
			
		||||
                FDSNDataSelect(
 | 
			
		||||
                    self.__inv,
 | 
			
		||||
                    self.__bufferSize,
 | 
			
		||||
                    self.__access,
 | 
			
		||||
                    {"mail": utils.u_str(avatarId), "blacklisted": False},
 | 
			
		||||
                ),
 | 
			
		||||
                lambda: None,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
@implementer(portal.IRealm)
 | 
			
		||||
class FDSNDataSelectAuthRealm:
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def __init__(self, inv, bufferSize, access, userdb):
 | 
			
		||||
        self.__inv = inv
 | 
			
		||||
        self.__bufferSize = bufferSize
 | 
			
		||||
        self.__access = access
 | 
			
		||||
        self.__userdb = userdb
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def requestAvatar(self, avatarId, _mind, *interfaces_):
 | 
			
		||||
        if resource.IResource in interfaces_:
 | 
			
		||||
            return (
 | 
			
		||||
                resource.IResource,
 | 
			
		||||
                FDSNDataSelect(
 | 
			
		||||
                    self.__inv,
 | 
			
		||||
                    self.__bufferSize,
 | 
			
		||||
                    self.__access,
 | 
			
		||||
                    self.__userdb.getAttributes(utils.u_str(avatarId)),
 | 
			
		||||
                ),
 | 
			
		||||
                lambda: None,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
class FDSNDataSelect(BaseResource):
 | 
			
		||||
    isLeaf = True
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def __init__(self, inv, bufferSize, access=None, user=None):
 | 
			
		||||
        super().__init__(VERSION)
 | 
			
		||||
 | 
			
		||||
        self._rsURL = Application.Instance().recordStreamURL()
 | 
			
		||||
        self.__inv = inv
 | 
			
		||||
        self.__access = access
 | 
			
		||||
        self.__user = user
 | 
			
		||||
        self.__bufferSize = bufferSize
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def render_OPTIONS(self, req):
 | 
			
		||||
        req.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
 | 
			
		||||
        req.setHeader(
 | 
			
		||||
            "Access-Control-Allow-Headers",
 | 
			
		||||
            "Accept, Content-Type, X-Requested-With, Origin",
 | 
			
		||||
        )
 | 
			
		||||
        req.setHeader("Content-Type", "text/plain; charset=utf-8")
 | 
			
		||||
        return ""
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def render_GET(self, req):
 | 
			
		||||
        # Parse and validate GET parameters
 | 
			
		||||
        ro = _DataSelectRequestOptions()
 | 
			
		||||
        ro.userName = self.__user and self.__user.get("mail")
 | 
			
		||||
        try:
 | 
			
		||||
            ro.parseGET(req.args)
 | 
			
		||||
            ro.parse()
 | 
			
		||||
            # the GET operation supports exactly one stream filter
 | 
			
		||||
            ro.streams.append(ro)
 | 
			
		||||
        except ValueError as e:
 | 
			
		||||
            logging.warning(str(e))
 | 
			
		||||
            return self.renderErrorPage(req, http.BAD_REQUEST, str(e), ro)
 | 
			
		||||
 | 
			
		||||
        return self._processRequest(req, ro)
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def render_POST(self, req):
 | 
			
		||||
        # Parse and validate POST parameters
 | 
			
		||||
        ro = _DataSelectRequestOptions()
 | 
			
		||||
        ro.userName = self.__user and self.__user.get("mail")
 | 
			
		||||
        try:
 | 
			
		||||
            ro.parsePOST(req.content)
 | 
			
		||||
            ro.parse()
 | 
			
		||||
        except ValueError as e:
 | 
			
		||||
            logging.warning(str(e))
 | 
			
		||||
            return self.renderErrorPage(req, http.BAD_REQUEST, str(e), ro)
 | 
			
		||||
 | 
			
		||||
        return self._processRequest(req, ro)
 | 
			
		||||
 | 
			
		||||
    # -----------------------------------------------------------------------
 | 
			
		||||
    def _networkIter(self, ro):
 | 
			
		||||
        for i in range(self.__inv.networkCount()):
 | 
			
		||||
            net = self.__inv.network(i)
 | 
			
		||||
 | 
			
		||||
            # network code
 | 
			
		||||
            if ro.channel and not ro.channel.matchNet(net.code()):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            # start and end time
 | 
			
		||||
            if ro.time:
 | 
			
		||||
                try:
 | 
			
		||||
                    end = net.end()
 | 
			
		||||
                except ValueError:
 | 
			
		||||
                    end = None
 | 
			
		||||
                if not ro.time.match(net.start(), end):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
            yield net
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _stationIter(net, ro):
 | 
			
		||||
        for i in range(net.stationCount()):
 | 
			
		||||
            sta = net.station(i)
 | 
			
		||||
 | 
			
		||||
            # station code
 | 
			
		||||
            if ro.channel and not ro.channel.matchSta(sta.code()):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            # start and end time
 | 
			
		||||
            if ro.time:
 | 
			
		||||
                try:
 | 
			
		||||
                    end = sta.end()
 | 
			
		||||
                except ValueError:
 | 
			
		||||
                    end = None
 | 
			
		||||
                if not ro.time.match(sta.start(), end):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
            yield sta
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _locationIter(sta, ro):
 | 
			
		||||
        for i in range(sta.sensorLocationCount()):
 | 
			
		||||
            loc = sta.sensorLocation(i)
 | 
			
		||||
 | 
			
		||||
            # location code
 | 
			
		||||
            if ro.channel and not ro.channel.matchLoc(loc.code()):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            # start and end time
 | 
			
		||||
            if ro.time:
 | 
			
		||||
                try:
 | 
			
		||||
                    end = loc.end()
 | 
			
		||||
                except ValueError:
 | 
			
		||||
                    end = None
 | 
			
		||||
                if not ro.time.match(loc.start(), end):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
            yield loc
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _streamIter(loc, ro):
 | 
			
		||||
        for i in range(loc.streamCount()):
 | 
			
		||||
            stream = loc.stream(i)
 | 
			
		||||
 | 
			
		||||
            # stream code
 | 
			
		||||
            if ro.channel and not ro.channel.matchCha(stream.code()):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            # start and end time
 | 
			
		||||
            if ro.time:
 | 
			
		||||
                try:
 | 
			
		||||
                    end = stream.end()
 | 
			
		||||
                except ValueError:
 | 
			
		||||
                    end = None
 | 
			
		||||
                if not ro.time.match(stream.start(), end):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
            yield stream, False
 | 
			
		||||
 | 
			
		||||
        for i in range(loc.auxStreamCount()):
 | 
			
		||||
            stream = loc.auxStream(i)
 | 
			
		||||
 | 
			
		||||
            # stream code
 | 
			
		||||
            if ro.channel and not ro.channel.matchCha(stream.code()):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            # start and end time
 | 
			
		||||
            if ro.time:
 | 
			
		||||
                try:
 | 
			
		||||
                    end = stream.end()
 | 
			
		||||
                except ValueError:
 | 
			
		||||
                    end = None
 | 
			
		||||
                if not ro.time.match(stream.start(), end):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
            yield stream, True
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def _processRequest(self, req, ro):
 | 
			
		||||
        # pylint: disable=W0212
 | 
			
		||||
 | 
			
		||||
        if ro.quality not in ("B", "M"):
 | 
			
		||||
            msg = "quality other than 'B' or 'M' not supported"
 | 
			
		||||
            return self.renderErrorPage(req, http.BAD_REQUEST, msg, ro)
 | 
			
		||||
 | 
			
		||||
        if ro.minimumLength:
 | 
			
		||||
            msg = "enforcing of minimum record length not supported"
 | 
			
		||||
            return self.renderErrorPage(req, http.BAD_REQUEST, msg, ro)
 | 
			
		||||
 | 
			
		||||
        if ro.longestOnly:
 | 
			
		||||
            msg = "limitation to longest segment not supported"
 | 
			
		||||
            return self.renderErrorPage(req, http.BAD_REQUEST, msg, ro)
 | 
			
		||||
 | 
			
		||||
        app = Application.Instance()
 | 
			
		||||
        ro._checkTimes(app._realtimeGap)
 | 
			
		||||
 | 
			
		||||
        maxSamples = None
 | 
			
		||||
        if app._samplesM is not None:
 | 
			
		||||
            maxSamples = app._samplesM * 1000000
 | 
			
		||||
            samples = 0
 | 
			
		||||
 | 
			
		||||
        trackerList = []
 | 
			
		||||
        userIP = ""
 | 
			
		||||
 | 
			
		||||
        if app._trackdbEnabled or app._requestLog:
 | 
			
		||||
            xff = req.requestHeaders.getRawHeaders("x-forwarded-for")
 | 
			
		||||
            if xff:
 | 
			
		||||
                userIP = xff[0].split(",")[0].strip()
 | 
			
		||||
            else:
 | 
			
		||||
                userIP = req.getClientIP()
 | 
			
		||||
 | 
			
		||||
            clientID = req.getHeader("User-Agent")
 | 
			
		||||
            if clientID:
 | 
			
		||||
                clientID = clientID[:80]
 | 
			
		||||
            else:
 | 
			
		||||
                clientID = "fdsnws"
 | 
			
		||||
 | 
			
		||||
        if app._trackdbEnabled:
 | 
			
		||||
            if ro.userName:
 | 
			
		||||
                userID = ro.userName
 | 
			
		||||
            else:
 | 
			
		||||
                userID = app._trackdbDefaultUser
 | 
			
		||||
 | 
			
		||||
            reqID = f"ws{str(int(round(time.time() * 1000) - 1420070400000))}"
 | 
			
		||||
            tracker = RequestTrackerDB(
 | 
			
		||||
                clientID,
 | 
			
		||||
                app.connection(),
 | 
			
		||||
                reqID,
 | 
			
		||||
                "WAVEFORM",
 | 
			
		||||
                userID,
 | 
			
		||||
                f"REQUEST WAVEFORM {reqID}",
 | 
			
		||||
                "fdsnws",
 | 
			
		||||
                userIP,
 | 
			
		||||
                req.getClientIP(),
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            trackerList.append(tracker)
 | 
			
		||||
 | 
			
		||||
        if app._requestLog:
 | 
			
		||||
            tracker = app._requestLog.tracker(ro.service, ro.userName, userIP, clientID)
 | 
			
		||||
            trackerList.append(tracker)
 | 
			
		||||
 | 
			
		||||
        # Open record stream
 | 
			
		||||
        rs = _MyRecordStream(self._rsURL, trackerList, self.__bufferSize)
 | 
			
		||||
 | 
			
		||||
        forbidden = None
 | 
			
		||||
        auxStreamsFound = False
 | 
			
		||||
 | 
			
		||||
        # Add request streams
 | 
			
		||||
        # iterate over inventory networks
 | 
			
		||||
        for s in ro.streams:
 | 
			
		||||
            for net in self._networkIter(s):
 | 
			
		||||
                netRestricted = utils.isRestricted(net)
 | 
			
		||||
                if not trackerList and netRestricted and not self.__user:
 | 
			
		||||
                    forbidden = forbidden or (forbidden is None)
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                for sta in self._stationIter(net, s):
 | 
			
		||||
                    staRestricted = utils.isRestricted(sta)
 | 
			
		||||
                    if not trackerList and staRestricted and not self.__user:
 | 
			
		||||
                        forbidden = forbidden or (forbidden is None)
 | 
			
		||||
                        continue
 | 
			
		||||
 | 
			
		||||
                    for loc in self._locationIter(sta, s):
 | 
			
		||||
                        for cha, aux in self._streamIter(loc, s):
 | 
			
		||||
                            start_time = max(cha.start(), s.time.start)
 | 
			
		||||
 | 
			
		||||
                            try:
 | 
			
		||||
                                end_time = min(cha.end(), s.time.end)
 | 
			
		||||
                            except ValueError:
 | 
			
		||||
                                end_time = s.time.end
 | 
			
		||||
 | 
			
		||||
                            streamRestricted = (
 | 
			
		||||
                                netRestricted
 | 
			
		||||
                                or staRestricted
 | 
			
		||||
                                or utils.isRestricted(cha)
 | 
			
		||||
                            )
 | 
			
		||||
                            if streamRestricted and (
 | 
			
		||||
                                not self.__user
 | 
			
		||||
                                or (
 | 
			
		||||
                                    self.__access
 | 
			
		||||
                                    and not self.__access.authorize(
 | 
			
		||||
                                        self.__user,
 | 
			
		||||
                                        net.code(),
 | 
			
		||||
                                        sta.code(),
 | 
			
		||||
                                        loc.code(),
 | 
			
		||||
                                        cha.code(),
 | 
			
		||||
                                        start_time,
 | 
			
		||||
                                        end_time,
 | 
			
		||||
                                    )
 | 
			
		||||
                                )
 | 
			
		||||
                            ):
 | 
			
		||||
                                for tracker in trackerList:
 | 
			
		||||
                                    net_class = (
 | 
			
		||||
                                        "t" if net.code()[0] in "0123456789XYZ" else "p"
 | 
			
		||||
                                    )
 | 
			
		||||
                                    tracker.line_status(
 | 
			
		||||
                                        start_time,
 | 
			
		||||
                                        end_time,
 | 
			
		||||
                                        net.code(),
 | 
			
		||||
                                        sta.code(),
 | 
			
		||||
                                        cha.code(),
 | 
			
		||||
                                        loc.code(),
 | 
			
		||||
                                        True,
 | 
			
		||||
                                        net_class,
 | 
			
		||||
                                        True,
 | 
			
		||||
                                        [],
 | 
			
		||||
                                        "fdsnws",
 | 
			
		||||
                                        "DENIED",
 | 
			
		||||
                                        0,
 | 
			
		||||
                                        "",
 | 
			
		||||
                                    )
 | 
			
		||||
 | 
			
		||||
                                forbidden = forbidden or (forbidden is None)
 | 
			
		||||
                                continue
 | 
			
		||||
 | 
			
		||||
                            forbidden = False
 | 
			
		||||
 | 
			
		||||
                            # aux streams are deprecated, mark aux streams as
 | 
			
		||||
                            # present to report warning later on, also do not
 | 
			
		||||
                            # count aux stream samples due to their loose
 | 
			
		||||
                            # binding to a aux device and source which only
 | 
			
		||||
                            # optionally contains a sampling rate
 | 
			
		||||
                            if aux:
 | 
			
		||||
                                auxStreamsFound = True
 | 
			
		||||
                            # enforce maximum sample per request restriction
 | 
			
		||||
                            elif maxSamples is not None:
 | 
			
		||||
                                try:
 | 
			
		||||
                                    n = cha.sampleRateNumerator()
 | 
			
		||||
                                    d = cha.sampleRateDenominator()
 | 
			
		||||
                                except ValueError:
 | 
			
		||||
                                    logging.warning(
 | 
			
		||||
                                        "skipping stream without sampling rate "
 | 
			
		||||
                                        f"definition: {net.code()}.{sta.code()}."
 | 
			
		||||
                                        f"{loc.code()}.{cha.code()}"
 | 
			
		||||
                                    )
 | 
			
		||||
                                    continue
 | 
			
		||||
 | 
			
		||||
                                # calculate number of samples for requested
 | 
			
		||||
                                # time window
 | 
			
		||||
                                diffSec = (end_time - start_time).length()
 | 
			
		||||
                                samples += int(diffSec * n / d)
 | 
			
		||||
                                if samples > maxSamples:
 | 
			
		||||
                                    msg = (
 | 
			
		||||
                                        f"maximum number of {app._samplesM}M samples "
 | 
			
		||||
                                        "exceeded"
 | 
			
		||||
                                    )
 | 
			
		||||
                                    return self.renderErrorPage(
 | 
			
		||||
                                        req, http.REQUEST_ENTITY_TOO_LARGE, msg, ro
 | 
			
		||||
                                    )
 | 
			
		||||
 | 
			
		||||
                            logging.debug(
 | 
			
		||||
                                f"adding stream: {net.code()}.{sta.code()}.{loc.code()}"
 | 
			
		||||
                                f".{cha.code()} {start_time.iso()} - {end_time.iso()}"
 | 
			
		||||
                            )
 | 
			
		||||
                            rs.addStream(
 | 
			
		||||
                                net.code(),
 | 
			
		||||
                                sta.code(),
 | 
			
		||||
                                loc.code(),
 | 
			
		||||
                                cha.code(),
 | 
			
		||||
                                start_time,
 | 
			
		||||
                                end_time,
 | 
			
		||||
                                utils.isRestricted(cha),
 | 
			
		||||
                                sta.archiveNetworkCode(),
 | 
			
		||||
                            )
 | 
			
		||||
 | 
			
		||||
        if forbidden:
 | 
			
		||||
            for tracker in trackerList:
 | 
			
		||||
                tracker.volume_status("fdsnws", "DENIED", 0, "")
 | 
			
		||||
                tracker.request_status("END", "")
 | 
			
		||||
 | 
			
		||||
            msg = "access denied"
 | 
			
		||||
            return self.renderErrorPage(req, http.FORBIDDEN, msg, ro)
 | 
			
		||||
 | 
			
		||||
        if forbidden is None:
 | 
			
		||||
            for tracker in trackerList:
 | 
			
		||||
                tracker.volume_status("fdsnws", "NODATA", 0, "")
 | 
			
		||||
                tracker.request_status("END", "")
 | 
			
		||||
 | 
			
		||||
            msg = "no metadata found"
 | 
			
		||||
            return self.renderErrorPage(req, http.NO_CONTENT, msg, ro)
 | 
			
		||||
 | 
			
		||||
        if auxStreamsFound:
 | 
			
		||||
            msg = (
 | 
			
		||||
                "the request contains at least one auxiliary stream which are "
 | 
			
		||||
                "deprecated"
 | 
			
		||||
            )
 | 
			
		||||
            if maxSamples is not None:
 | 
			
		||||
                msg += (
 | 
			
		||||
                    " and whose samples are not included in the maximum sample per "
 | 
			
		||||
                    "request limit"
 | 
			
		||||
                )
 | 
			
		||||
            logging.info(msg)
 | 
			
		||||
 | 
			
		||||
        # Build output filename
 | 
			
		||||
        fileName = (
 | 
			
		||||
            Application.Instance()._fileNamePrefix.replace(
 | 
			
		||||
                "%time", time.strftime("%Y-%m-%dT%H:%M:%S")
 | 
			
		||||
            )
 | 
			
		||||
            + ".mseed"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Create producer for async IO
 | 
			
		||||
        prod = _WaveformProducer(req, ro, rs, fileName, trackerList)
 | 
			
		||||
        req.registerProducer(prod, True)
 | 
			
		||||
        prod.resumeProducing()
 | 
			
		||||
 | 
			
		||||
        # The request is handled by the deferred object
 | 
			
		||||
        return server.NOT_DONE_YET
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# vim: ts=4 et
 | 
			
		||||
							
								
								
									
										1020
									
								
								lib/python/seiscomp/fdsnws/event.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1020
									
								
								lib/python/seiscomp/fdsnws/event.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										216
									
								
								lib/python/seiscomp/fdsnws/fastsds.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								lib/python/seiscomp/fdsnws/fastsds.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,216 @@
 | 
			
		||||
################################################################################
 | 
			
		||||
# Copyright (C) 2014-2017 by GFZ Potsdam
 | 
			
		||||
#
 | 
			
		||||
# Classes to access an SDS structure to be used by the Dataselect-WS
 | 
			
		||||
#
 | 
			
		||||
# Author:  Javier Quinteros
 | 
			
		||||
# Email:   javier@gfz-potsdam.de
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
import datetime
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
import seiscomp.logging
 | 
			
		||||
import seiscomp.mseedlite
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SDS:
 | 
			
		||||
    def __init__(self, sdsRoot):
 | 
			
		||||
        if isinstance(sdsRoot, list):
 | 
			
		||||
            self.sdsRoot = sdsRoot
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            self.sdsRoot = [sdsRoot]
 | 
			
		||||
 | 
			
		||||
    def __getMSName(self, reqDate, net, sta, loc, cha):
 | 
			
		||||
        for root in self.sdsRoot:
 | 
			
		||||
            yield (
 | 
			
		||||
                f"{root}/{reqDate.year}/{net}/{sta}/{cha}.D/{net}.{sta}.{loc}.{cha}.D."
 | 
			
		||||
                f"{reqDate.year}.{reqDate.strftime('%j')}"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def __time2recno(msFile, reclen, timeStart, recStart, timeEnd, recEnd, searchTime):
 | 
			
		||||
        if searchTime <= timeStart:
 | 
			
		||||
            msFile.seek(recStart * reclen)
 | 
			
		||||
            rec = seiscomp.mseedlite.Record(msFile)
 | 
			
		||||
            return (recStart, rec.end_time)
 | 
			
		||||
 | 
			
		||||
        if searchTime >= timeEnd:
 | 
			
		||||
            msFile.seek(recEnd * reclen)
 | 
			
		||||
            rec = seiscomp.mseedlite.Record(msFile)
 | 
			
		||||
            return (recEnd, rec.end_time)
 | 
			
		||||
 | 
			
		||||
        t1 = timeStart
 | 
			
		||||
        r1 = recStart
 | 
			
		||||
        t2 = timeEnd
 | 
			
		||||
        r2 = recEnd
 | 
			
		||||
        rn = int(
 | 
			
		||||
            r1
 | 
			
		||||
            + (r2 - r1) * (searchTime - t1).total_seconds() / (t2 - t1).total_seconds()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        rn = max(rn, recStart)
 | 
			
		||||
        rn = min(rn, recEnd)
 | 
			
		||||
 | 
			
		||||
        while True:
 | 
			
		||||
            msFile.seek(rn * reclen)
 | 
			
		||||
            rec = seiscomp.mseedlite.Record(msFile)
 | 
			
		||||
 | 
			
		||||
            if rec.begin_time < searchTime:
 | 
			
		||||
                r1 = rn
 | 
			
		||||
                t1 = rec.begin_time
 | 
			
		||||
 | 
			
		||||
                if t1 == t2:
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
                rn = int(
 | 
			
		||||
                    r1
 | 
			
		||||
                    + (r2 - r1)
 | 
			
		||||
                    * (searchTime - t1).total_seconds()
 | 
			
		||||
                    / (t2 - t1).total_seconds()
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                rn = max(rn, recStart)
 | 
			
		||||
                rn = min(rn, recEnd)
 | 
			
		||||
 | 
			
		||||
                if rn == r1:
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
            else:
 | 
			
		||||
                r2 = rn
 | 
			
		||||
                t2 = rec.begin_time
 | 
			
		||||
 | 
			
		||||
                if t1 == t2:
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
                rn = int(
 | 
			
		||||
                    r2
 | 
			
		||||
                    - (r2 - r1)
 | 
			
		||||
                    * (t2 - searchTime).total_seconds()
 | 
			
		||||
                    / (t2 - t1).total_seconds()
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                rn = max(rn, recStart)
 | 
			
		||||
                rn = min(rn, recEnd)
 | 
			
		||||
 | 
			
		||||
                if rn == r2:
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
        return rn, rec.end_time
 | 
			
		||||
 | 
			
		||||
    def __getWaveform(self, startt, endt, msFile, bufferSize):
 | 
			
		||||
        if startt >= endt:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        rec = seiscomp.mseedlite.Record(msFile)
 | 
			
		||||
        reclen = rec.size
 | 
			
		||||
        recStart = 0
 | 
			
		||||
        timeStart = rec.begin_time
 | 
			
		||||
 | 
			
		||||
        if rec.begin_time >= endt:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        msFile.seek(-reclen, 2)
 | 
			
		||||
        rec = seiscomp.mseedlite.Record(msFile)
 | 
			
		||||
        recEnd = msFile.tell() // reclen - 1
 | 
			
		||||
        timeEnd = rec.begin_time
 | 
			
		||||
 | 
			
		||||
        if rec.end_time <= startt:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if timeStart >= timeEnd:
 | 
			
		||||
            seiscomp.logging.error(
 | 
			
		||||
                f"{msFile.name}: overlap detected (start={timeStart}, end={timeEnd})"
 | 
			
		||||
            )
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        (lower, _) = self.__time2recno(
 | 
			
		||||
            msFile, reclen, timeStart, recStart, timeEnd, recEnd, startt
 | 
			
		||||
        )
 | 
			
		||||
        (upper, _) = self.__time2recno(
 | 
			
		||||
            msFile, reclen, startt, lower, timeEnd, recEnd, endt
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if upper < lower:
 | 
			
		||||
            seiscomp.logging.error(
 | 
			
		||||
                f"{msFile.name}: overlap detected (lower={lower}, upper={upper})"
 | 
			
		||||
            )
 | 
			
		||||
            upper = lower
 | 
			
		||||
 | 
			
		||||
        msFile.seek(lower * reclen)
 | 
			
		||||
        remaining = (upper - lower + 1) * reclen
 | 
			
		||||
        check = True
 | 
			
		||||
 | 
			
		||||
        if bufferSize % reclen:
 | 
			
		||||
            bufferSize += reclen - bufferSize % reclen
 | 
			
		||||
 | 
			
		||||
        while remaining > 0:
 | 
			
		||||
            size = min(remaining, bufferSize)
 | 
			
		||||
            data = msFile.read(size)
 | 
			
		||||
            remaining -= size
 | 
			
		||||
            offset = 0
 | 
			
		||||
 | 
			
		||||
            if not data:
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            if check:
 | 
			
		||||
                while offset < len(data):
 | 
			
		||||
                    rec = seiscomp.mseedlite.Record(data[offset : offset + reclen])
 | 
			
		||||
 | 
			
		||||
                    if rec.begin_time >= endt:
 | 
			
		||||
                        return
 | 
			
		||||
 | 
			
		||||
                    if rec.end_time > startt:
 | 
			
		||||
                        break
 | 
			
		||||
 | 
			
		||||
                    offset += reclen
 | 
			
		||||
 | 
			
		||||
                check = False
 | 
			
		||||
 | 
			
		||||
            if offset < len(data):
 | 
			
		||||
                yield data[offset:] if offset else data
 | 
			
		||||
 | 
			
		||||
        while True:
 | 
			
		||||
            data = msFile.read(reclen)
 | 
			
		||||
 | 
			
		||||
            if not data:
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            rec = seiscomp.mseedlite.Record(data)
 | 
			
		||||
 | 
			
		||||
            if rec.begin_time >= endt:
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            yield data
 | 
			
		||||
 | 
			
		||||
    def __getDayRaw(self, day, startt, endt, net, sta, loc, cha, bufferSize):
 | 
			
		||||
        # Take into account the case of empty location
 | 
			
		||||
        if loc == "--":
 | 
			
		||||
            loc = ""
 | 
			
		||||
 | 
			
		||||
        for dataFile in self.__getMSName(day, net, sta, loc, cha):
 | 
			
		||||
            if not os.path.exists(dataFile):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                with open(dataFile, "rb") as msFile:
 | 
			
		||||
                    for buf in self.__getWaveform(startt, endt, msFile, bufferSize):
 | 
			
		||||
                        yield buf
 | 
			
		||||
 | 
			
		||||
            except seiscomp.mseedlite.MSeedError as e:
 | 
			
		||||
                seiscomp.logging.error(f"{dataFile}: {e}")
 | 
			
		||||
 | 
			
		||||
    def getRawBytes(self, startt, endt, net, sta, loc, cha, bufferSize):
 | 
			
		||||
        day = datetime.datetime(
 | 
			
		||||
            startt.year, startt.month, startt.day
 | 
			
		||||
        ) - datetime.timedelta(days=1)
 | 
			
		||||
        endDay = datetime.datetime(endt.year, endt.month, endt.day)
 | 
			
		||||
 | 
			
		||||
        while day <= endDay:
 | 
			
		||||
            for buf in self.__getDayRaw(
 | 
			
		||||
                day, startt, endt, net, sta, loc, cha, bufferSize
 | 
			
		||||
            ):
 | 
			
		||||
                yield buf
 | 
			
		||||
 | 
			
		||||
            day += datetime.timedelta(days=1)
 | 
			
		||||
							
								
								
									
										296
									
								
								lib/python/seiscomp/fdsnws/http.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								lib/python/seiscomp/fdsnws/http.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,296 @@
 | 
			
		||||
################################################################################
 | 
			
		||||
# Copyright (C) 2013-2014 by gempa GmbH
 | 
			
		||||
#
 | 
			
		||||
# HTTP -- Utility methods which generate HTTP result strings
 | 
			
		||||
#
 | 
			
		||||
# Author:  Stephan Herrnkind
 | 
			
		||||
# Email:   herrnkind@gempa.de
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
from twisted.web import http, resource, server, static, util
 | 
			
		||||
 | 
			
		||||
import seiscomp.core
 | 
			
		||||
import seiscomp.logging
 | 
			
		||||
 | 
			
		||||
from .utils import accessLog, b_str, u_str, writeTSBin
 | 
			
		||||
 | 
			
		||||
VERSION = "1.2.5"
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTP:
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def renderErrorPage(request, code, msg, version=VERSION, ro=None):
 | 
			
		||||
        resp = b"""\
 | 
			
		||||
Error %i: %s
 | 
			
		||||
 | 
			
		||||
%s
 | 
			
		||||
 | 
			
		||||
Usage details are available from %s
 | 
			
		||||
 | 
			
		||||
Request:
 | 
			
		||||
%s
 | 
			
		||||
 | 
			
		||||
Request Submitted:
 | 
			
		||||
%s
 | 
			
		||||
 | 
			
		||||
Service Version:
 | 
			
		||||
%s
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
        noContent = code == http.NO_CONTENT
 | 
			
		||||
 | 
			
		||||
        # rewrite response code if requested and no data was found
 | 
			
		||||
        if noContent and ro is not None:
 | 
			
		||||
            code = ro.noData
 | 
			
		||||
 | 
			
		||||
        # set response code
 | 
			
		||||
        request.setResponseCode(code)
 | 
			
		||||
 | 
			
		||||
        # status code 204 requires no message body
 | 
			
		||||
        if code == http.NO_CONTENT:
 | 
			
		||||
            response = b""
 | 
			
		||||
        else:
 | 
			
		||||
            request.setHeader("Content-Type", "text/plain; charset=utf-8")
 | 
			
		||||
 | 
			
		||||
            reference = b"%s/" % request.path.rpartition(b"/")[0]
 | 
			
		||||
 | 
			
		||||
            codeStr = http.RESPONSES[code]
 | 
			
		||||
            date = b_str(seiscomp.core.Time.GMT().toString("%FT%T.%f"))
 | 
			
		||||
            response = resp % (
 | 
			
		||||
                code,
 | 
			
		||||
                codeStr,
 | 
			
		||||
                b_str(msg),
 | 
			
		||||
                reference,
 | 
			
		||||
                request.uri,
 | 
			
		||||
                date,
 | 
			
		||||
                b_str(version),
 | 
			
		||||
            )
 | 
			
		||||
            if not noContent:
 | 
			
		||||
                seiscomp.logging.warning(
 | 
			
		||||
                    f"responding with error: {code} ({u_str(codeStr)})"
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
        accessLog(request, ro, code, len(response), msg)
 | 
			
		||||
        return response
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def renderNotFound(request, version=VERSION):
 | 
			
		||||
        msg = "The requested resource does not exist on this server."
 | 
			
		||||
        return HTTP.renderErrorPage(request, http.NOT_FOUND, msg, version)
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def renderNotModified(request, ro=None):
 | 
			
		||||
        code = http.NOT_MODIFIED
 | 
			
		||||
        request.setResponseCode(code)
 | 
			
		||||
        request.responseHeaders.removeHeader("Content-Type")
 | 
			
		||||
        accessLog(request, ro, code, 0, None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
class ServiceVersion(resource.Resource):
 | 
			
		||||
    isLeaf = True
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def __init__(self, version):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
 | 
			
		||||
        self.version = version
 | 
			
		||||
        self.type = "text/plain"
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def render(self, request):
 | 
			
		||||
        request.setHeader("Content-Type", "text/plain; charset=utf-8")
 | 
			
		||||
        return b_str(self.version)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
class WADLFilter(static.Data):
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def __init__(self, path, paramNameFilterList):
 | 
			
		||||
        data = ""
 | 
			
		||||
        removeParam = False
 | 
			
		||||
        with open(path, "r", encoding="utf-8") as fp:
 | 
			
		||||
            for line in fp:
 | 
			
		||||
                lineStripped = line.strip().replace(" ", "")
 | 
			
		||||
                if removeParam:
 | 
			
		||||
                    if "</param>" in lineStripped:
 | 
			
		||||
                        removeParam = False
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                valid = True
 | 
			
		||||
                if "<param" in lineStripped:
 | 
			
		||||
                    for f in paramNameFilterList:
 | 
			
		||||
                        if f'name="{f}"' in lineStripped:
 | 
			
		||||
                            valid = False
 | 
			
		||||
                            if lineStripped[-2:] != "/>":
 | 
			
		||||
                                removeParam = True
 | 
			
		||||
                            break
 | 
			
		||||
 | 
			
		||||
                if valid:
 | 
			
		||||
                    data += line
 | 
			
		||||
 | 
			
		||||
        super().__init__(b_str(data), "application/xml; charset=utf-8")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
class BaseResource(resource.Resource):
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def __init__(self, version=VERSION):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
 | 
			
		||||
        self.version = version
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def renderErrorPage(self, request, code, msg, ro=None):
 | 
			
		||||
        return HTTP.renderErrorPage(request, code, msg, self.version, ro)
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def writeErrorPage(self, request, code, msg, ro=None):
 | 
			
		||||
        data = self.renderErrorPage(request, code, msg, ro)
 | 
			
		||||
        if data:
 | 
			
		||||
            writeTSBin(request, data)
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def returnNotModified(self, request, ro=None):
 | 
			
		||||
        HTTP.renderNotModified(request, ro)
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    # Renders error page if the result set exceeds the configured maximum number
 | 
			
		||||
    # objects
 | 
			
		||||
    def checkObjects(self, request, objCount, maxObj):
 | 
			
		||||
        if objCount <= maxObj:
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        msg = (
 | 
			
		||||
            "The result set of your request exceeds the configured maximum "
 | 
			
		||||
            f"number of objects ({maxObj}). Refine your request parameters."
 | 
			
		||||
        )
 | 
			
		||||
        self.writeErrorPage(request, http.REQUEST_ENTITY_TOO_LARGE, msg)
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
class NoResource(BaseResource):
 | 
			
		||||
    isLeaf = True
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def render(self, request):
 | 
			
		||||
        return HTTP.renderNotFound(request, self.version)
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def getChild(self, _path, _request):
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
class ListingResource(BaseResource):
 | 
			
		||||
    html = """<!doctype html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
  <meta charset="utf-8">
 | 
			
		||||
  <meta name="author" content="gempa GmbH">
 | 
			
		||||
  <title>SeisComP FDSNWS Implementation</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
  <p><a href="../">Parent Directory</a></p>
 | 
			
		||||
  <h1>SeisComP FDSNWS Web Service</h1>
 | 
			
		||||
  <p>Index of %s</p>
 | 
			
		||||
  <ul>
 | 
			
		||||
%s
 | 
			
		||||
  </ul>
 | 
			
		||||
</body>"""
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def render(self, request):
 | 
			
		||||
        lis = ""
 | 
			
		||||
        if request.path[-1:] != b"/":
 | 
			
		||||
            return util.redirectTo(request.path + b"/", request)
 | 
			
		||||
 | 
			
		||||
        for k, v in self.children.items():
 | 
			
		||||
            if v.isLeaf:
 | 
			
		||||
                continue
 | 
			
		||||
            if hasattr(v, "hideInListing") and v.hideInListing:
 | 
			
		||||
                continue
 | 
			
		||||
            name = u_str(k)
 | 
			
		||||
            lis += f'<li><a href="{name}/">{name}/</a></li>\n'
 | 
			
		||||
 | 
			
		||||
        return b_str(ListingResource.html % (u_str(request.path), lis))
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def getChild(self, path, _request):
 | 
			
		||||
        if not path:
 | 
			
		||||
            return self
 | 
			
		||||
 | 
			
		||||
        return NoResource(self.version)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
class DirectoryResource(static.File):
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def __init__(self, fileName, version=VERSION):
 | 
			
		||||
        super().__init__(fileName)
 | 
			
		||||
 | 
			
		||||
        self.version = version
 | 
			
		||||
        self.childNotFound = NoResource(self.version)
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def render(self, request):
 | 
			
		||||
        if request.path[-1:] != b"/":
 | 
			
		||||
            return util.redirectTo(request.path + b"/", request)
 | 
			
		||||
 | 
			
		||||
        return static.File.render(self, request)
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def getChild(self, path, _request):
 | 
			
		||||
        if not path:
 | 
			
		||||
            return self
 | 
			
		||||
 | 
			
		||||
        return NoResource(self.version)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
class Site(server.Site):
 | 
			
		||||
    def __init__(self, res, corsOrigins):
 | 
			
		||||
        super().__init__(res)
 | 
			
		||||
 | 
			
		||||
        self._corsOrigins = corsOrigins
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def getResourceFor(self, request):
 | 
			
		||||
        seiscomp.logging.debug(
 | 
			
		||||
            f"request ({request.getClientIP()}): {u_str(request.uri)}"
 | 
			
		||||
        )
 | 
			
		||||
        request.setHeader("Server", f"SeisComP-FDSNWS/{VERSION}")
 | 
			
		||||
        request.setHeader("Access-Control-Allow-Headers", "Authorization")
 | 
			
		||||
        request.setHeader("Access-Control-Expose-Headers", "WWW-Authenticate")
 | 
			
		||||
 | 
			
		||||
        self.setAllowOrigin(request)
 | 
			
		||||
 | 
			
		||||
        return server.Site.getResourceFor(self, request)
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def setAllowOrigin(self, req):
 | 
			
		||||
        # no allowed origin: no response header
 | 
			
		||||
        lenOrigins = len(self._corsOrigins)
 | 
			
		||||
        if lenOrigins == 0:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # one origin: add header
 | 
			
		||||
        if lenOrigins == 1:
 | 
			
		||||
            req.setHeader("Access-Control-Allow-Origin", self._corsOrigins[0])
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # more than one origin: check current origin against allowed origins
 | 
			
		||||
        # and return the current origin on match.
 | 
			
		||||
        origin = req.getHeader("Origin")
 | 
			
		||||
        if origin in self._corsOrigins:
 | 
			
		||||
            req.setHeader("Access-Control-Allow-Origin", origin)
 | 
			
		||||
 | 
			
		||||
            # Set Vary header to let the browser know that the response depends
 | 
			
		||||
            # on the request. Certain cache strategies should be disabled.
 | 
			
		||||
            req.setHeader("Vary", "Origin")
 | 
			
		||||
							
								
								
									
										101
									
								
								lib/python/seiscomp/fdsnws/log.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								lib/python/seiscomp/fdsnws/log.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,101 @@
 | 
			
		||||
################################################################################
 | 
			
		||||
# Copyright (C) 2013-2014 gempa GmbH
 | 
			
		||||
#
 | 
			
		||||
# Thread-safe file logger
 | 
			
		||||
#
 | 
			
		||||
# Author:  Stephan Herrnkind
 | 
			
		||||
# Email:   herrnkind@gempa.de
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
import threading
 | 
			
		||||
 | 
			
		||||
from queue import Queue
 | 
			
		||||
 | 
			
		||||
# -------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _worker(log):
 | 
			
		||||
    while True:
 | 
			
		||||
        # pylint: disable=W0212
 | 
			
		||||
        msg = log._queue.get()
 | 
			
		||||
        log._write(str(msg))
 | 
			
		||||
        log._queue.task_done()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
class Log:
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def __init__(self, filePath, archiveSize=7):
 | 
			
		||||
        self._filePath = filePath
 | 
			
		||||
        self._basePath = os.path.dirname(filePath)
 | 
			
		||||
        self._fileName = os.path.basename(filePath)
 | 
			
		||||
        self._archiveSize = archiveSize
 | 
			
		||||
        self._queue = Queue()
 | 
			
		||||
        self._lastLogTime = None
 | 
			
		||||
        self._fd = None
 | 
			
		||||
 | 
			
		||||
        self._archiveSize = max(self._archiveSize, 0)
 | 
			
		||||
 | 
			
		||||
        # worker thread, responsible for writing messages to file
 | 
			
		||||
        t = threading.Thread(target=_worker, args=(self,))
 | 
			
		||||
        t.daemon = True
 | 
			
		||||
        t.start()
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def __del__(self):
 | 
			
		||||
        # wait for worker thread to write all pending log messages
 | 
			
		||||
        self._queue.join()
 | 
			
		||||
 | 
			
		||||
        if self._fd is not None:
 | 
			
		||||
            self._fd.close()
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def log(self, msg):
 | 
			
		||||
        self._queue.put(msg)
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def _rotate(self):
 | 
			
		||||
        self._fd.close()
 | 
			
		||||
        self._fd = None
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            pattern = f"{self._filePath}.%i"
 | 
			
		||||
            for i in range(self._archiveSize, 1, -1):
 | 
			
		||||
                src = pattern % (i - 1)
 | 
			
		||||
                if os.path.isfile(src):
 | 
			
		||||
                    os.rename(pattern % (i - 1), pattern % i)
 | 
			
		||||
            os.rename(self._filePath, pattern % 1)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            print(f"failed to rotate access log: {e}", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
        self._fd = open(self._filePath, "w", encoding="utf-8")
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def _write(self, msg):
 | 
			
		||||
        try:
 | 
			
		||||
            now = time.localtime()
 | 
			
		||||
            if self._fd is None:
 | 
			
		||||
                if self._basePath and not os.path.exists(self._basePath):
 | 
			
		||||
                    os.makedirs(self._basePath)
 | 
			
		||||
                self._fd = open(self._filePath, "a", encoding="utf-8")
 | 
			
		||||
            elif (
 | 
			
		||||
                self._archiveSize > 0
 | 
			
		||||
                and self._lastLogTime is not None
 | 
			
		||||
                and (
 | 
			
		||||
                    self._lastLogTime.tm_yday != now.tm_yday
 | 
			
		||||
                    or self._lastLogTime.tm_year != now.tm_year
 | 
			
		||||
                )
 | 
			
		||||
            ):
 | 
			
		||||
                self._rotate()
 | 
			
		||||
 | 
			
		||||
            print(msg, file=self._fd)
 | 
			
		||||
            self._fd.flush()
 | 
			
		||||
            self._lastLogTime = now
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            print(f"access log: {e}", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# vim: ts=4 et
 | 
			
		||||
							
								
								
									
										138
									
								
								lib/python/seiscomp/fdsnws/reqlog.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								lib/python/seiscomp/fdsnws/reqlog.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,138 @@
 | 
			
		||||
import os
 | 
			
		||||
import datetime
 | 
			
		||||
import json
 | 
			
		||||
import hashlib
 | 
			
		||||
import subprocess
 | 
			
		||||
import logging
 | 
			
		||||
import logging.handlers
 | 
			
		||||
import threading
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from .utils import b_str
 | 
			
		||||
 | 
			
		||||
mutex = threading.Lock()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MyFileHandler(logging.handlers.TimedRotatingFileHandler):
 | 
			
		||||
    def __init__(self, filename):
 | 
			
		||||
        super().__init__(filename, when="midnight", utc=True)
 | 
			
		||||
 | 
			
		||||
    def rotate(self, source, dest):
 | 
			
		||||
        super().rotate(source, dest)
 | 
			
		||||
 | 
			
		||||
        if os.path.exists(dest):
 | 
			
		||||
            subprocess.Popen(["bzip2", dest])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Tracker:
 | 
			
		||||
    def __init__(self, logger, geoip, service, userName, userIP, clientID, userSalt):
 | 
			
		||||
        self.__logger = logger
 | 
			
		||||
        self.__userName = userName
 | 
			
		||||
        self.__userSalt = userSalt
 | 
			
		||||
        self.__logged = False
 | 
			
		||||
 | 
			
		||||
        if userName:
 | 
			
		||||
            userID = int(
 | 
			
		||||
                hashlib.md5(b_str(userSalt + userName.lower())).hexdigest()[:8], 16
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            userID = int(hashlib.md5(b_str(userSalt + userIP)).hexdigest()[:8], 16)
 | 
			
		||||
 | 
			
		||||
        self.__data = {
 | 
			
		||||
            "service": service,
 | 
			
		||||
            "userID": userID,
 | 
			
		||||
            "clientID": clientID,
 | 
			
		||||
            "userEmail": None,
 | 
			
		||||
            "auth": bool(userName),
 | 
			
		||||
            "userLocation": {},
 | 
			
		||||
            "created": f"{datetime.datetime.utcnow().isoformat()}Z",
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if geoip:
 | 
			
		||||
            self.__data["userLocation"]["country"] = geoip.country_code_by_addr(userIP)
 | 
			
		||||
 | 
			
		||||
        if (
 | 
			
		||||
            userName and userName.lower().endswith("@gfz-potsdam.de")
 | 
			
		||||
        ) or userIP.startswith("139.17."):
 | 
			
		||||
            self.__data["userLocation"]["institution"] = "GFZ"
 | 
			
		||||
 | 
			
		||||
    # pylint: disable=W0613
 | 
			
		||||
    def line_status(
 | 
			
		||||
        self,
 | 
			
		||||
        start_time,
 | 
			
		||||
        end_time,
 | 
			
		||||
        network,
 | 
			
		||||
        station,
 | 
			
		||||
        channel,
 | 
			
		||||
        location,
 | 
			
		||||
        restricted,
 | 
			
		||||
        net_class,
 | 
			
		||||
        shared,
 | 
			
		||||
        constraints,
 | 
			
		||||
        volume,
 | 
			
		||||
        status,
 | 
			
		||||
        size,
 | 
			
		||||
        message,
 | 
			
		||||
    ):
 | 
			
		||||
        try:
 | 
			
		||||
            trace = self.__data["trace"]
 | 
			
		||||
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            trace = []
 | 
			
		||||
            self.__data["trace"] = trace
 | 
			
		||||
 | 
			
		||||
        trace.append(
 | 
			
		||||
            {
 | 
			
		||||
                "net": network,
 | 
			
		||||
                "sta": station,
 | 
			
		||||
                "loc": location,
 | 
			
		||||
                "cha": channel,
 | 
			
		||||
                "start": start_time.iso(),
 | 
			
		||||
                "end": end_time.iso(),
 | 
			
		||||
                "restricted": restricted,
 | 
			
		||||
                "status": status,
 | 
			
		||||
                "bytes": size,
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if restricted and status == "OK":
 | 
			
		||||
            self.__data["userEmail"] = self.__userName
 | 
			
		||||
 | 
			
		||||
    # FDSNWS requests have one volume, so volume_status() is called once per request
 | 
			
		||||
    def volume_status(self, volume, status, size, message):
 | 
			
		||||
        self.__data["status"] = status
 | 
			
		||||
        self.__data["bytes"] = size
 | 
			
		||||
        self.__data["finished"] = f"{datetime.datetime.utcnow().isoformat()}Z"
 | 
			
		||||
 | 
			
		||||
    def request_status(self, status, message):
 | 
			
		||||
        with mutex:
 | 
			
		||||
            if not self.__logged:
 | 
			
		||||
                self.__logger.info(json.dumps(self.__data))
 | 
			
		||||
                self.__logged = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RequestLog:
 | 
			
		||||
    def __init__(self, filename, userSalt):
 | 
			
		||||
        self.__logger = logging.getLogger("seiscomp.fdsnws.reqlog")
 | 
			
		||||
        self.__logger.addHandler(MyFileHandler(filename))
 | 
			
		||||
        self.__logger.setLevel(logging.INFO)
 | 
			
		||||
        self.__userSalt = userSalt
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            import GeoIP
 | 
			
		||||
 | 
			
		||||
            self.__geoip = GeoIP.new(GeoIP.GEOIP_MEMORY_CACHE)
 | 
			
		||||
 | 
			
		||||
        except ImportError:
 | 
			
		||||
            self.__geoip = None
 | 
			
		||||
 | 
			
		||||
    def tracker(self, service, userName, userIP, clientID):
 | 
			
		||||
        return Tracker(
 | 
			
		||||
            self.__logger,
 | 
			
		||||
            self.__geoip,
 | 
			
		||||
            service,
 | 
			
		||||
            userName,
 | 
			
		||||
            userIP,
 | 
			
		||||
            clientID,
 | 
			
		||||
            self.__userSalt,
 | 
			
		||||
        )
 | 
			
		||||
							
								
								
									
										179
									
								
								lib/python/seiscomp/fdsnws/reqtrack.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								lib/python/seiscomp/fdsnws/reqtrack.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,179 @@
 | 
			
		||||
from twisted.internet import reactor
 | 
			
		||||
import seiscomp.core
 | 
			
		||||
import seiscomp.datamodel
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def callFromThread(f):
 | 
			
		||||
    def wrap(*args, **kwargs):
 | 
			
		||||
        reactor.callFromThread(f, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    return wrap
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def enableNotifier(f):
 | 
			
		||||
    def wrap(*args, **kwargs):
 | 
			
		||||
        saveState = seiscomp.datamodel.Notifier.IsEnabled()
 | 
			
		||||
        seiscomp.datamodel.Notifier.SetEnabled(True)
 | 
			
		||||
        f(*args, **kwargs)
 | 
			
		||||
        seiscomp.datamodel.Notifier.SetEnabled(saveState)
 | 
			
		||||
 | 
			
		||||
    return wrap
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RequestTrackerDB(object):
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        appName,
 | 
			
		||||
        msgConn,
 | 
			
		||||
        req_id,
 | 
			
		||||
        req_type,
 | 
			
		||||
        user,
 | 
			
		||||
        header,
 | 
			
		||||
        label,
 | 
			
		||||
        user_ip,
 | 
			
		||||
        client_ip,
 | 
			
		||||
    ):
 | 
			
		||||
        self.msgConn = msgConn
 | 
			
		||||
        self.arclinkRequest = seiscomp.datamodel.ArclinkRequest.Create()
 | 
			
		||||
        self.arclinkRequest.setCreated(seiscomp.core.Time.GMT())
 | 
			
		||||
        self.arclinkRequest.setRequestID(req_id)
 | 
			
		||||
        self.arclinkRequest.setUserID(str(user))
 | 
			
		||||
        self.arclinkRequest.setClientID(appName)
 | 
			
		||||
        if user_ip:
 | 
			
		||||
            self.arclinkRequest.setUserIP(user_ip)
 | 
			
		||||
        if client_ip:
 | 
			
		||||
            self.arclinkRequest.setClientIP(client_ip)
 | 
			
		||||
        self.arclinkRequest.setType(req_type)
 | 
			
		||||
        self.arclinkRequest.setLabel(label)
 | 
			
		||||
        self.arclinkRequest.setHeader(header)
 | 
			
		||||
 | 
			
		||||
        self.averageTimeWindow = seiscomp.core.TimeSpan(0.0)
 | 
			
		||||
        self.totalLineCount = 0
 | 
			
		||||
        self.okLineCount = 0
 | 
			
		||||
 | 
			
		||||
        self.requestLines = []
 | 
			
		||||
        self.statusLines = []
 | 
			
		||||
 | 
			
		||||
    def send(self):
 | 
			
		||||
        msg = seiscomp.datamodel.Notifier.GetMessage(True)
 | 
			
		||||
        if msg:
 | 
			
		||||
            self.msgConn.send("LOGGING", msg)
 | 
			
		||||
 | 
			
		||||
    def line_status(
 | 
			
		||||
        self,
 | 
			
		||||
        start_time,
 | 
			
		||||
        end_time,
 | 
			
		||||
        network,
 | 
			
		||||
        station,
 | 
			
		||||
        channel,
 | 
			
		||||
        location,
 | 
			
		||||
        restricted,
 | 
			
		||||
        net_class,
 | 
			
		||||
        shared,
 | 
			
		||||
        constraints,
 | 
			
		||||
        volume,
 | 
			
		||||
        status,
 | 
			
		||||
        size,
 | 
			
		||||
        message,
 | 
			
		||||
    ):
 | 
			
		||||
        if network is None or network == "":
 | 
			
		||||
            network = "."
 | 
			
		||||
        if station is None or station == "":
 | 
			
		||||
            station = "."
 | 
			
		||||
        if channel is None or channel == "":
 | 
			
		||||
            channel = "."
 | 
			
		||||
        if location is None or location == "":
 | 
			
		||||
            location = "."
 | 
			
		||||
        if volume is None:
 | 
			
		||||
            volume = "NODATA"
 | 
			
		||||
        if size is None:
 | 
			
		||||
            size = 0
 | 
			
		||||
        if message is None:
 | 
			
		||||
            message = ""
 | 
			
		||||
 | 
			
		||||
        if isinstance(constraints, list):
 | 
			
		||||
            constr = " ".join(constraints)
 | 
			
		||||
        else:
 | 
			
		||||
            constr = " ".join([f"{a}={b}" for (a, b) in constraints.items()])
 | 
			
		||||
 | 
			
		||||
        arclinkRequestLine = seiscomp.datamodel.ArclinkRequestLine()
 | 
			
		||||
        arclinkRequestLine.setStart(start_time)
 | 
			
		||||
        arclinkRequestLine.setEnd(end_time)
 | 
			
		||||
        arclinkRequestLine.setStreamID(
 | 
			
		||||
            seiscomp.datamodel.WaveformStreamID(
 | 
			
		||||
                network[:8], station[:8], location[:8], channel[:8], ""
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        arclinkRequestLine.setConstraints(constr)
 | 
			
		||||
        if isinstance(restricted, bool):
 | 
			
		||||
            arclinkRequestLine.setRestricted(restricted)
 | 
			
		||||
        arclinkRequestLine.setNetClass(net_class)
 | 
			
		||||
        if isinstance(shared, bool):
 | 
			
		||||
            arclinkRequestLine.setShared(shared)
 | 
			
		||||
        #
 | 
			
		||||
        arclinkStatusLine = seiscomp.datamodel.ArclinkStatusLine()
 | 
			
		||||
        arclinkStatusLine.setVolumeID(volume)
 | 
			
		||||
        arclinkStatusLine.setStatus(status)
 | 
			
		||||
        arclinkStatusLine.setSize(size)
 | 
			
		||||
        arclinkStatusLine.setMessage(message)
 | 
			
		||||
        #
 | 
			
		||||
        arclinkRequestLine.setStatus(arclinkStatusLine)
 | 
			
		||||
        self.requestLines.append(arclinkRequestLine)
 | 
			
		||||
 | 
			
		||||
        self.averageTimeWindow += end_time - start_time
 | 
			
		||||
        self.totalLineCount += 1
 | 
			
		||||
        if status == "OK":
 | 
			
		||||
            self.okLineCount += 1
 | 
			
		||||
 | 
			
		||||
    def volume_status(self, volume, status, size, message):
 | 
			
		||||
        if volume is None:
 | 
			
		||||
            volume = "NODATA"
 | 
			
		||||
        if size is None:
 | 
			
		||||
            size = 0
 | 
			
		||||
        if message is None:
 | 
			
		||||
            message = ""
 | 
			
		||||
 | 
			
		||||
        arclinkStatusLine = seiscomp.datamodel.ArclinkStatusLine()
 | 
			
		||||
        arclinkStatusLine.setVolumeID(volume)
 | 
			
		||||
        arclinkStatusLine.setStatus(status)
 | 
			
		||||
        arclinkStatusLine.setSize(size)
 | 
			
		||||
        arclinkStatusLine.setMessage(message)
 | 
			
		||||
        self.statusLines.append(arclinkStatusLine)
 | 
			
		||||
 | 
			
		||||
    @callFromThread
 | 
			
		||||
    @enableNotifier
 | 
			
		||||
    def request_status(self, status, message):
 | 
			
		||||
        if message is None:
 | 
			
		||||
            message = ""
 | 
			
		||||
 | 
			
		||||
        self.arclinkRequest.setStatus(status)
 | 
			
		||||
        self.arclinkRequest.setMessage(message)
 | 
			
		||||
 | 
			
		||||
        ars = seiscomp.datamodel.ArclinkRequestSummary()
 | 
			
		||||
        tw = self.averageTimeWindow.seconds()
 | 
			
		||||
        if self.totalLineCount > 0:
 | 
			
		||||
            # avarage request time window
 | 
			
		||||
            tw = self.averageTimeWindow.seconds() // self.totalLineCount
 | 
			
		||||
        if tw >= 2**31:
 | 
			
		||||
            tw = -1  # prevent 32bit int overflow
 | 
			
		||||
        ars.setAverageTimeWindow(tw)
 | 
			
		||||
        ars.setTotalLineCount(self.totalLineCount)
 | 
			
		||||
        ars.setOkLineCount(self.okLineCount)
 | 
			
		||||
        self.arclinkRequest.setSummary(ars)
 | 
			
		||||
 | 
			
		||||
        al = seiscomp.datamodel.ArclinkLog()
 | 
			
		||||
        al.add(self.arclinkRequest)
 | 
			
		||||
 | 
			
		||||
        for obj in self.requestLines:
 | 
			
		||||
            self.arclinkRequest.add(obj)
 | 
			
		||||
 | 
			
		||||
        for obj in self.statusLines:
 | 
			
		||||
            self.arclinkRequest.add(obj)
 | 
			
		||||
 | 
			
		||||
        self.send()
 | 
			
		||||
 | 
			
		||||
    def __verseed_errors(self, volume):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def verseed(self, volume, file):
 | 
			
		||||
        pass
 | 
			
		||||
							
								
								
									
										609
									
								
								lib/python/seiscomp/fdsnws/request.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										609
									
								
								lib/python/seiscomp/fdsnws/request.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,609 @@
 | 
			
		||||
################################################################################
 | 
			
		||||
# Copyright (C) 2013-2014 gempa GmbH
 | 
			
		||||
#
 | 
			
		||||
# RequestOptions -- HTTP GET request parameters
 | 
			
		||||
#
 | 
			
		||||
# Author:  Stephan Herrnkind
 | 
			
		||||
# Email:   herrnkind@gempa.de
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
import fnmatch
 | 
			
		||||
import math
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
from twisted.web import http
 | 
			
		||||
 | 
			
		||||
from seiscomp.core import Time
 | 
			
		||||
import seiscomp.logging
 | 
			
		||||
import seiscomp.math
 | 
			
		||||
 | 
			
		||||
from .utils import u_str
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RequestOptions:
 | 
			
		||||
    # the match() method matched only patterns at the beginning of a string,
 | 
			
		||||
    # since we have to ensure that no invalid character is present we use the
 | 
			
		||||
    # search() method in combination with a negated pattern instead
 | 
			
		||||
    FloatChars = re.compile(r"[^-0-9.]").search
 | 
			
		||||
    ChannelChars = re.compile(r"[^A-Za-z0-9*?]").search
 | 
			
		||||
    ChannelExtChars = re.compile(r"[^A-Za-z0-9*?+\-_]").search
 | 
			
		||||
    BooleanTrueValues = ["1", "true", "t", "yes", "y"]
 | 
			
		||||
    BooleanFalseValues = ["0", "false", "f", "no", "n"]
 | 
			
		||||
    OutputFormats = []  # override in derived classes
 | 
			
		||||
 | 
			
		||||
    PStart = ["starttime", "start"]
 | 
			
		||||
    PEnd = ["endtime", "end"]
 | 
			
		||||
    PStartBefore = ["startbefore"]
 | 
			
		||||
    PStartAfter = ["startafter"]
 | 
			
		||||
    PEndBefore = ["endbefore"]
 | 
			
		||||
    PEndAfter = ["endafter"]
 | 
			
		||||
    SimpleTimeParams = PStart + PEnd
 | 
			
		||||
    WindowTimeParams = PStartBefore + PStartAfter + PEndBefore + PEndAfter
 | 
			
		||||
    TimeParams = SimpleTimeParams + WindowTimeParams
 | 
			
		||||
 | 
			
		||||
    PNet = ["network", "net"]
 | 
			
		||||
    PSta = ["station", "sta"]
 | 
			
		||||
    PLoc = ["location", "loc"]
 | 
			
		||||
    PCha = ["channel", "cha"]
 | 
			
		||||
    StreamParams = PNet + PSta + PLoc + PCha
 | 
			
		||||
 | 
			
		||||
    PMinLat = ["minlatitude", "minlat"]
 | 
			
		||||
    PMaxLat = ["maxlatitude", "maxlat"]
 | 
			
		||||
    PMinLon = ["minlongitude", "minlon"]
 | 
			
		||||
    PMaxLon = ["maxlongitude", "maxlon"]
 | 
			
		||||
    PLat = ["latitude", "lat"]
 | 
			
		||||
    PLon = ["longitude", "lon"]
 | 
			
		||||
    PMinRadius = ["minradius"]
 | 
			
		||||
    PMaxRadius = ["maxradius"]
 | 
			
		||||
    GeoRectParams = PMinLat + PMaxLat + PMinLon + PMaxLon
 | 
			
		||||
    GeoCircleParams = PLat + PLon + PMinRadius + PMaxRadius
 | 
			
		||||
    GeoParams = GeoRectParams + GeoCircleParams
 | 
			
		||||
 | 
			
		||||
    PFormat = ["format"]
 | 
			
		||||
    PNoData = ["nodata"]
 | 
			
		||||
    OutputParams = PFormat + PNoData
 | 
			
		||||
 | 
			
		||||
    POSTParams = OutputParams
 | 
			
		||||
    GETParams = StreamParams + SimpleTimeParams
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    class Channel:
 | 
			
		||||
        def __init__(self):
 | 
			
		||||
            self.net = None
 | 
			
		||||
            self.sta = None
 | 
			
		||||
            self.loc = None
 | 
			
		||||
            self.cha = None
 | 
			
		||||
 | 
			
		||||
        def matchNet(self, value):
 | 
			
		||||
            return self.match(value, self.net)
 | 
			
		||||
 | 
			
		||||
        def matchSta(self, value):
 | 
			
		||||
            return self.match(value, self.sta)
 | 
			
		||||
 | 
			
		||||
        def matchLoc(self, value):
 | 
			
		||||
            return self.match(value, self.loc, True)
 | 
			
		||||
 | 
			
		||||
        def matchCha(self, value):
 | 
			
		||||
            return self.match(value, self.cha)
 | 
			
		||||
 | 
			
		||||
        @staticmethod
 | 
			
		||||
        def match(value, globList, testEmpty=False):
 | 
			
		||||
            if not globList:
 | 
			
		||||
                return True
 | 
			
		||||
 | 
			
		||||
            for glob in globList:
 | 
			
		||||
                if testEmpty and value == "" and glob == "--":
 | 
			
		||||
                    return True
 | 
			
		||||
                if fnmatch.fnmatchcase(value, glob):
 | 
			
		||||
                    return True
 | 
			
		||||
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    class Time:
 | 
			
		||||
        def __init__(self):
 | 
			
		||||
            self.simpleTime = True
 | 
			
		||||
            self.start = None
 | 
			
		||||
            self.end = None
 | 
			
		||||
            # window time only
 | 
			
		||||
            self.startBefore = None
 | 
			
		||||
            self.startAfter = None
 | 
			
		||||
            self.endBefore = None
 | 
			
		||||
            self.endAfter = None
 | 
			
		||||
 | 
			
		||||
        # used by FDSN Station and DataSelect
 | 
			
		||||
        def match(self, start, end=None):
 | 
			
		||||
            # simple time: limit to epochs intersecting with the specified time
 | 
			
		||||
            # range
 | 
			
		||||
            res = (self.start is None or end is None or end >= self.start) and (
 | 
			
		||||
                self.end is None or start <= self.end
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # window time: limit to epochs strictly starting or ending before or
 | 
			
		||||
            # after a specified time value
 | 
			
		||||
            if not self.simpleTime:
 | 
			
		||||
                res = (
 | 
			
		||||
                    res
 | 
			
		||||
                    and (
 | 
			
		||||
                        self.startBefore is None
 | 
			
		||||
                        or (start is not None and start < self.startBefore)
 | 
			
		||||
                    )
 | 
			
		||||
                    and (
 | 
			
		||||
                        self.startAfter is None
 | 
			
		||||
                        or (start is not None and start > self.startAfter)
 | 
			
		||||
                    )
 | 
			
		||||
                    and (
 | 
			
		||||
                        self.endBefore is None
 | 
			
		||||
                        or (end is not None and end < self.endBefore)
 | 
			
		||||
                    )
 | 
			
		||||
                    and (self.endAfter is None or end is None or end > self.endAfter)
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            return res
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    class Geo:
 | 
			
		||||
        # -----------------------------------------------------------------------
 | 
			
		||||
        class BBox:
 | 
			
		||||
            def __init__(self):
 | 
			
		||||
                self.minLat = None
 | 
			
		||||
                self.maxLat = None
 | 
			
		||||
                self.minLon = None
 | 
			
		||||
                self.maxLon = None
 | 
			
		||||
 | 
			
		||||
            def dateLineCrossing(self):
 | 
			
		||||
                return self.minLon and self.maxLon and self.minLon > self.maxLon
 | 
			
		||||
 | 
			
		||||
        # -----------------------------------------------------------------------
 | 
			
		||||
        class BCircle:
 | 
			
		||||
            def __init__(self):
 | 
			
		||||
                self.lat = None
 | 
			
		||||
                self.lon = None
 | 
			
		||||
                self.minRad = None
 | 
			
		||||
                self.maxRad = None
 | 
			
		||||
 | 
			
		||||
            # -------------------------------------------------------------------
 | 
			
		||||
            # Calculates outer bounding box
 | 
			
		||||
            def calculateBBox(self):
 | 
			
		||||
                def rad(degree):
 | 
			
		||||
                    return math.radians(degree)
 | 
			
		||||
 | 
			
		||||
                def deg(radians):
 | 
			
		||||
                    return math.degrees(radians)
 | 
			
		||||
 | 
			
		||||
                b = RequestOptions.Geo.BBox()
 | 
			
		||||
                if self.maxRad is None or self.maxRad >= 180:
 | 
			
		||||
                    return b
 | 
			
		||||
 | 
			
		||||
                b.minLat = self.lat - self.maxRad
 | 
			
		||||
                b.maxLat = self.lat + self.maxRad
 | 
			
		||||
                if b.minLat > -90 and b.maxLat < 90:
 | 
			
		||||
                    dLon = deg(
 | 
			
		||||
                        math.asin(math.sin(rad(self.maxRad) / math.cos(rad(self.lat))))
 | 
			
		||||
                    )
 | 
			
		||||
                    b.minLon = self.lon - dLon
 | 
			
		||||
                    if b.minLon < -180:
 | 
			
		||||
                        b.minLon += 360
 | 
			
		||||
                    b.maxLon = self.lon + dLon
 | 
			
		||||
                    if b.maxLon > 180:
 | 
			
		||||
                        b.maxLon -= 360
 | 
			
		||||
                else:
 | 
			
		||||
                    # pole within distance: one latitude and no longitude
 | 
			
		||||
                    # restrictions remains
 | 
			
		||||
                    if b.minLat <= -90:
 | 
			
		||||
                        b.minLat = None
 | 
			
		||||
                    else:
 | 
			
		||||
                        b.maxLat = None
 | 
			
		||||
                    b.minLon = None
 | 
			
		||||
                    b.maxLon = None
 | 
			
		||||
 | 
			
		||||
                return b
 | 
			
		||||
 | 
			
		||||
        # -----------------------------------------------------------------------
 | 
			
		||||
        def __init__(self):
 | 
			
		||||
            self.bBox = None
 | 
			
		||||
            self.bCircle = None
 | 
			
		||||
 | 
			
		||||
        # -----------------------------------------------------------------------
 | 
			
		||||
        def match(self, lat, lon):
 | 
			
		||||
            if self.bBox is not None:
 | 
			
		||||
                b = self.bBox
 | 
			
		||||
                if b.minLat is not None and lat < b.minLat:
 | 
			
		||||
                    return False
 | 
			
		||||
                if b.maxLat is not None and lat > b.maxLat:
 | 
			
		||||
                    return False
 | 
			
		||||
                # date line crossing if minLon > maxLon
 | 
			
		||||
                if b.dateLineCrossing():
 | 
			
		||||
                    return lon >= b.minLon or lon <= b.maxLon
 | 
			
		||||
                if b.minLon is not None and lon < b.minLon:
 | 
			
		||||
                    return False
 | 
			
		||||
                if b.maxLon is not None and lon > b.maxLon:
 | 
			
		||||
                    return False
 | 
			
		||||
                return True
 | 
			
		||||
 | 
			
		||||
            if self.bCircle:
 | 
			
		||||
                c = self.bCircle
 | 
			
		||||
                dist = seiscomp.math.delazi(c.lat, c.lon, lat, lon)
 | 
			
		||||
                if c.minRad is not None and dist[0] < c.minRad:
 | 
			
		||||
                    return False
 | 
			
		||||
                if c.maxRad is not None and dist[0] > c.maxRad:
 | 
			
		||||
                    return False
 | 
			
		||||
                return True
 | 
			
		||||
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.service = ""
 | 
			
		||||
        self.accessTime = Time.GMT()
 | 
			
		||||
        self.userName = None
 | 
			
		||||
 | 
			
		||||
        self.time = None
 | 
			
		||||
        self.channel = None
 | 
			
		||||
        self.geo = None
 | 
			
		||||
 | 
			
		||||
        self.noData = http.NO_CONTENT
 | 
			
		||||
        self.format = None
 | 
			
		||||
 | 
			
		||||
        self._args = {}
 | 
			
		||||
        self.streams = []  # 1 entry for GET, multipl
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def parseOutput(self):
 | 
			
		||||
        # nodata
 | 
			
		||||
        code = self.parseInt(self.PNoData)
 | 
			
		||||
        if code is not None:
 | 
			
		||||
            if code not in (http.NO_CONTENT, http.NOT_FOUND):
 | 
			
		||||
                self.raiseValueError(self.PNoData[0])
 | 
			
		||||
            self.noData = code
 | 
			
		||||
 | 
			
		||||
        # format
 | 
			
		||||
        key, value = self.getFirstValue(self.PFormat)
 | 
			
		||||
        if value is None:
 | 
			
		||||
            # no format specified: default to first in list if available
 | 
			
		||||
            if len(self.OutputFormats) > 0:
 | 
			
		||||
                self.format = self.OutputFormats[0]
 | 
			
		||||
        else:
 | 
			
		||||
            value = value.lower()
 | 
			
		||||
            if value in self.OutputFormats:
 | 
			
		||||
                self.format = value
 | 
			
		||||
            else:
 | 
			
		||||
                self.raiseValueError(key)
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def parseChannel(self):
 | 
			
		||||
        c = RequestOptions.Channel()
 | 
			
		||||
 | 
			
		||||
        c.net = self.parseChannelChars(self.PNet, False, True)
 | 
			
		||||
        c.sta = self.parseChannelChars(self.PSta)
 | 
			
		||||
        c.loc = self.parseChannelChars(self.PLoc, True)
 | 
			
		||||
        c.cha = self.parseChannelChars(self.PCha)
 | 
			
		||||
 | 
			
		||||
        if c.net or c.sta or c.loc or c.cha:
 | 
			
		||||
            self.channel = c
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def parseTime(self, parseWindowTime=False):
 | 
			
		||||
        t = RequestOptions.Time()
 | 
			
		||||
 | 
			
		||||
        # start[time], end[time]
 | 
			
		||||
        t.start = self.parseTimeStr(self.PStart)
 | 
			
		||||
        t.end = self.parseTimeStr(self.PEnd)
 | 
			
		||||
 | 
			
		||||
        simpleTime = t.start is not None or t.end is not None
 | 
			
		||||
 | 
			
		||||
        # [start,end][before,after]
 | 
			
		||||
        if parseWindowTime:
 | 
			
		||||
            t.startBefore = self.parseTimeStr(self.PStartBefore)
 | 
			
		||||
            t.startAfter = self.parseTimeStr(self.PStartAfter)
 | 
			
		||||
            t.endBefore = self.parseTimeStr(self.PEndBefore)
 | 
			
		||||
            t.endAfter = self.parseTimeStr(self.PEndAfter)
 | 
			
		||||
 | 
			
		||||
            windowTime = (
 | 
			
		||||
                t.startBefore is not None
 | 
			
		||||
                or t.startAfter is not None
 | 
			
		||||
                or t.endBefore is not None
 | 
			
		||||
                or t.endAfter is not None
 | 
			
		||||
            )
 | 
			
		||||
            if simpleTime or windowTime:
 | 
			
		||||
                self.time = t
 | 
			
		||||
                self.time.simpleTime = not windowTime
 | 
			
		||||
 | 
			
		||||
        elif simpleTime:
 | 
			
		||||
            self.time = t
 | 
			
		||||
            self.time.simpleTime = True
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def parseGeo(self):
 | 
			
		||||
        # bounding box (optional)
 | 
			
		||||
        b = RequestOptions.Geo.BBox()
 | 
			
		||||
        b.minLat = self.parseFloat(self.PMinLat, -90, 90)
 | 
			
		||||
        b.maxLat = self.parseFloat(self.PMaxLat, -90, 90)
 | 
			
		||||
        if b.minLat is not None and b.maxLat is not None and b.minLat > b.maxLat:
 | 
			
		||||
            raise ValueError(f"{self.PMinLat[0]} exceeds {self.PMaxLat[0]}")
 | 
			
		||||
 | 
			
		||||
        b.minLon = self.parseFloat(self.PMinLon, -180, 180)
 | 
			
		||||
        b.maxLon = self.parseFloat(self.PMaxLon, -180, 180)
 | 
			
		||||
        # maxLon < minLon -> date line crossing
 | 
			
		||||
 | 
			
		||||
        hasBBoxParam = (
 | 
			
		||||
            b.minLat is not None
 | 
			
		||||
            or b.maxLat is not None
 | 
			
		||||
            or b.minLon is not None
 | 
			
		||||
            or b.maxLon is not None
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # bounding circle (optional)
 | 
			
		||||
        c = RequestOptions.Geo.BCircle()
 | 
			
		||||
        c.lat = self.parseFloat(self.PLat, -90, 90)
 | 
			
		||||
        c.lon = self.parseFloat(self.PLon, -180, 180)
 | 
			
		||||
        c.minRad = self.parseFloat(self.PMinRadius, 0, 180)
 | 
			
		||||
        c.maxRad = self.parseFloat(self.PMaxRadius, 0, 180)
 | 
			
		||||
        if c.minRad is not None and c.maxRad is not None and c.minRad > c.maxRad:
 | 
			
		||||
            raise ValueError(f"{self.PMinRadius[0]} exceeds {self.PMaxRadius[0]}")
 | 
			
		||||
 | 
			
		||||
        hasBCircleRadParam = c.minRad is not None or c.maxRad is not None
 | 
			
		||||
        hasBCircleParam = c.lat is not None or c.lon is not None or hasBCircleRadParam
 | 
			
		||||
 | 
			
		||||
        # bounding box and bounding circle may not be combined
 | 
			
		||||
        if hasBBoxParam and hasBCircleParam:
 | 
			
		||||
            raise ValueError(
 | 
			
		||||
                "bounding box and bounding circle parameters may not be combined"
 | 
			
		||||
            )
 | 
			
		||||
        if hasBBoxParam:
 | 
			
		||||
            self.geo = RequestOptions.Geo()
 | 
			
		||||
            self.geo.bBox = b
 | 
			
		||||
        elif hasBCircleRadParam:
 | 
			
		||||
            self.geo = RequestOptions.Geo()
 | 
			
		||||
            if c.lat is None:
 | 
			
		||||
                c.lat = 0.0
 | 
			
		||||
            if c.lon is None:
 | 
			
		||||
                c.lon = 0.0
 | 
			
		||||
            self.geo.bCircle = c
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _assertValueRange(key, v, minValue, maxValue):
 | 
			
		||||
        if (minValue is not None and v < minValue) or (
 | 
			
		||||
            maxValue is not None and v > maxValue
 | 
			
		||||
        ):
 | 
			
		||||
            minStr, maxStr = "-inf", "inf"
 | 
			
		||||
            if minValue is not None:
 | 
			
		||||
                minStr = str(minValue)
 | 
			
		||||
            if maxValue is not None:
 | 
			
		||||
                maxStr = str(maxValue)
 | 
			
		||||
            raise ValueError(f"parameter not in domain [{minStr},{maxStr}]: {key}")
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def raiseValueError(key):
 | 
			
		||||
        raise ValueError(f"invalid value in parameter: {key}")
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def getFirstValue(self, keys):
 | 
			
		||||
        for key in keys:
 | 
			
		||||
            if key in self._args:
 | 
			
		||||
                return key, self._args[key][0].strip()
 | 
			
		||||
 | 
			
		||||
        return None, None
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def getValues(self, keys):
 | 
			
		||||
        v = []
 | 
			
		||||
        for key in keys:
 | 
			
		||||
            if key in self._args:
 | 
			
		||||
                v += self._args[key]
 | 
			
		||||
        return v
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def getListValues(self, keys, lower=False):
 | 
			
		||||
        values = set()
 | 
			
		||||
        for key in keys:
 | 
			
		||||
            if key not in self._args:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            for vList in self._args[key]:
 | 
			
		||||
                for v in vList.split(","):
 | 
			
		||||
                    if v is None:
 | 
			
		||||
                        continue
 | 
			
		||||
                    v = v.strip()
 | 
			
		||||
                    if lower:
 | 
			
		||||
                        v = v.lower()
 | 
			
		||||
                    values.add(v)
 | 
			
		||||
 | 
			
		||||
        return values
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def parseInt(self, keys, minValue=None, maxValue=None):
 | 
			
		||||
        key, value = self.getFirstValue(keys)
 | 
			
		||||
 | 
			
		||||
        if value is None:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            i = int(value)
 | 
			
		||||
        except ValueError as e:
 | 
			
		||||
            raise ValueError(f"invalid integer value in parameter: {key}") from e
 | 
			
		||||
 | 
			
		||||
        self._assertValueRange(key, i, minValue, maxValue)
 | 
			
		||||
        return i
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def parseFloat(self, keys, minValue=None, maxValue=None):
 | 
			
		||||
        key, value = self.getFirstValue(keys)
 | 
			
		||||
 | 
			
		||||
        if value is None:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        if self.FloatChars(value):
 | 
			
		||||
            raise ValueError(
 | 
			
		||||
                f"invalid characters in float parameter: {key} (scientific notation "
 | 
			
		||||
                "forbidden by spec)"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            f = float(value)
 | 
			
		||||
        except ValueError as e:
 | 
			
		||||
            raise ValueError(f"invalid float value in parameter: {key}") from e
 | 
			
		||||
 | 
			
		||||
        self._assertValueRange(key, f, minValue, maxValue)
 | 
			
		||||
        return f
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def parseBool(self, keys):
 | 
			
		||||
        key, value = self.getFirstValue(keys)
 | 
			
		||||
 | 
			
		||||
        if value is None:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        value = value.lower()
 | 
			
		||||
        if value in self.BooleanTrueValues:
 | 
			
		||||
            return True
 | 
			
		||||
        if value in self.BooleanFalseValues:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        raise ValueError(f"invalid boolean value in parameter: {key}")
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def parseTimeStr(self, keys):
 | 
			
		||||
        key, value = self.getFirstValue(keys)
 | 
			
		||||
 | 
			
		||||
        if value is None:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        time = Time.FromString(value)
 | 
			
		||||
        # use explicit test for None here since bool value for epoch date
 | 
			
		||||
        # (1970-01-01) is False
 | 
			
		||||
        if time is None:
 | 
			
		||||
            raise ValueError(f"invalid date format in parameter: {key}")
 | 
			
		||||
 | 
			
		||||
        return time
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def parseChannelChars(self, keys, allowEmpty=False, useExtChars=False):
 | 
			
		||||
        # channel parameters may be specified as a comma separated list and may
 | 
			
		||||
        # be repeated several times
 | 
			
		||||
        values = None
 | 
			
		||||
        for vList in self.getValues(keys):
 | 
			
		||||
            if values is None:
 | 
			
		||||
                values = []
 | 
			
		||||
            for v in vList.split(","):
 | 
			
		||||
                v = v.strip()
 | 
			
		||||
                if allowEmpty and (v == "--" or len(v) == 0):
 | 
			
		||||
                    values.append("--")
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                if (useExtChars and self.ChannelExtChars(v)) or (
 | 
			
		||||
                    not useExtChars and self.ChannelChars(v)
 | 
			
		||||
                ):
 | 
			
		||||
                    raise ValueError(f"invalid characters in parameter: {keys[0]}")
 | 
			
		||||
                values.append(v)
 | 
			
		||||
 | 
			
		||||
        return values
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def parseGET(self, args):
 | 
			
		||||
        # transform keys to lower case
 | 
			
		||||
        if args is not None:
 | 
			
		||||
            for k, v in args.items():
 | 
			
		||||
                k = u_str(k.lower())
 | 
			
		||||
                if k not in self.GETParams:
 | 
			
		||||
                    raise ValueError(f"invalid param: {k}")
 | 
			
		||||
 | 
			
		||||
                self._args[k] = [u_str(x) for x in v]
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def parsePOST(self, content):
 | 
			
		||||
        nLine = 0
 | 
			
		||||
 | 
			
		||||
        for line in content:
 | 
			
		||||
            nLine += 1
 | 
			
		||||
            line = u_str(line.strip())
 | 
			
		||||
 | 
			
		||||
            # ignore empty and comment lines
 | 
			
		||||
            if len(line) == 0 or line[0] == "#":
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            # collect parameter (non stream lines)
 | 
			
		||||
            toks = line.split("=", 1)
 | 
			
		||||
            if len(toks) > 1:
 | 
			
		||||
                key = toks[0].strip().lower()
 | 
			
		||||
 | 
			
		||||
                isPOSTParam = False
 | 
			
		||||
                for p in self.POSTParams:
 | 
			
		||||
                    if p == key:
 | 
			
		||||
                        if key not in self._args:
 | 
			
		||||
                            self._args[key] = []
 | 
			
		||||
                        self._args[key].append(toks[1].strip())
 | 
			
		||||
                        isPOSTParam = True
 | 
			
		||||
                        break
 | 
			
		||||
 | 
			
		||||
                if isPOSTParam:
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                # time parameters not allowed in POST header
 | 
			
		||||
                for p in self.TimeParams:
 | 
			
		||||
                    if p == key:
 | 
			
		||||
                        raise ValueError(
 | 
			
		||||
                            f"time parameter in line {nLine} not allowed in POST "
 | 
			
		||||
                            "request"
 | 
			
		||||
                        )
 | 
			
		||||
 | 
			
		||||
                # stream parameters not allowed in POST header
 | 
			
		||||
                for p in self.StreamParams:
 | 
			
		||||
                    if p == key:
 | 
			
		||||
                        raise ValueError(
 | 
			
		||||
                            f"stream parameter in line {nLine} not allowed in POST "
 | 
			
		||||
                            "request"
 | 
			
		||||
                        )
 | 
			
		||||
 | 
			
		||||
                raise ValueError(f"invalid parameter in line {nLine}")
 | 
			
		||||
 | 
			
		||||
            # stream parameters
 | 
			
		||||
            toks = line.split()
 | 
			
		||||
            nToks = len(toks)
 | 
			
		||||
            if nToks not in (5, 6):
 | 
			
		||||
                raise ValueError("invalid number of stream components in line {nLine}")
 | 
			
		||||
 | 
			
		||||
            ro = RequestOptions()
 | 
			
		||||
 | 
			
		||||
            # net, sta, loc, cha
 | 
			
		||||
            ro.channel = RequestOptions.Channel()
 | 
			
		||||
            ro.channel.net = toks[0].split(",")
 | 
			
		||||
            ro.channel.sta = toks[1].split(",")
 | 
			
		||||
            ro.channel.loc = toks[2].split(",")
 | 
			
		||||
            ro.channel.cha = toks[3].split(",")
 | 
			
		||||
 | 
			
		||||
            msg = "invalid %s value in line %i"
 | 
			
		||||
            for net in ro.channel.net:
 | 
			
		||||
                if ro.ChannelChars(net):
 | 
			
		||||
                    raise ValueError(msg % ("network", nLine))
 | 
			
		||||
            for sta in ro.channel.sta:
 | 
			
		||||
                if ro.ChannelChars(sta):
 | 
			
		||||
                    raise ValueError(msg % ("station", nLine))
 | 
			
		||||
            for loc in ro.channel.loc:
 | 
			
		||||
                if loc != "--" and ro.ChannelChars(loc):
 | 
			
		||||
                    raise ValueError(msg % ("location", nLine))
 | 
			
		||||
            for cha in ro.channel.cha:
 | 
			
		||||
                if ro.ChannelChars(cha):
 | 
			
		||||
                    raise ValueError(msg % ("channel", nLine))
 | 
			
		||||
 | 
			
		||||
            # start/end time
 | 
			
		||||
            ro.time = RequestOptions.Time()
 | 
			
		||||
            ro.time.start = Time.FromString(toks[4])
 | 
			
		||||
            logEnd = "-"
 | 
			
		||||
            if len(toks) > 5:
 | 
			
		||||
                ro.time.end = Time.FromString(toks[5])
 | 
			
		||||
                logEnd = ro.time.end.iso()
 | 
			
		||||
 | 
			
		||||
            seiscomp.logging.debug(
 | 
			
		||||
                f"ro: {ro.channel.net}.{ro.channel.sta}.{ro.channel.loc}."
 | 
			
		||||
                f"{ro.channel.cha} {ro.time.start.iso()} {logEnd}"
 | 
			
		||||
            )
 | 
			
		||||
            self.streams.append(ro)
 | 
			
		||||
 | 
			
		||||
        if not self.streams:
 | 
			
		||||
            raise ValueError("at least one stream line is required")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# vim: ts=4 et
 | 
			
		||||
							
								
								
									
										936
									
								
								lib/python/seiscomp/fdsnws/station.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										936
									
								
								lib/python/seiscomp/fdsnws/station.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,936 @@
 | 
			
		||||
################################################################################
 | 
			
		||||
# Copyright (C) 2013-2014 gempa GmbH
 | 
			
		||||
#
 | 
			
		||||
# FDSNStation -- Implements the fdsnws-station Web service, see
 | 
			
		||||
#   http://www.fdsn.org/webservices/
 | 
			
		||||
#
 | 
			
		||||
# Feature notes:
 | 
			
		||||
#   - 'updatedafter' request parameter not implemented: The last modification
 | 
			
		||||
#     time in SeisComP is tracked on the object level. If a child of an object
 | 
			
		||||
#     is updated the update time is not propagated to all parents. In order to
 | 
			
		||||
#     check if a station was updated all children must be evaluated recursively.
 | 
			
		||||
#     This operation would be much to expensive.
 | 
			
		||||
#   - additional request parameters:
 | 
			
		||||
#     - formatted:       boolean, default: false
 | 
			
		||||
#   - additional values of request parameters:
 | 
			
		||||
#     - format
 | 
			
		||||
#       - standard:      [xml, text]
 | 
			
		||||
#       - additional:    [fdsnxml (=xml), stationxml, sc3ml]
 | 
			
		||||
#       - default:       xml
 | 
			
		||||
#
 | 
			
		||||
# Author:  Stephan Herrnkind
 | 
			
		||||
# Email:   herrnkind@gempa.de
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
from twisted.internet.threads import deferToThread
 | 
			
		||||
from twisted.web import http, server
 | 
			
		||||
 | 
			
		||||
import seiscomp.datamodel
 | 
			
		||||
import seiscomp.logging
 | 
			
		||||
from seiscomp.client import Application
 | 
			
		||||
from seiscomp.core import Time
 | 
			
		||||
from seiscomp.io import Exporter, ExportObjectList
 | 
			
		||||
 | 
			
		||||
from .http import BaseResource
 | 
			
		||||
from .request import RequestOptions
 | 
			
		||||
from . import utils
 | 
			
		||||
 | 
			
		||||
VERSION = "1.1.6"
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _StationRequestOptions(RequestOptions):
 | 
			
		||||
    Exporters = {
 | 
			
		||||
        "xml": "fdsnxml",
 | 
			
		||||
        "fdsnxml": "fdsnxml",
 | 
			
		||||
        "stationxml": "staxml",
 | 
			
		||||
        "sc3ml": "trunk",
 | 
			
		||||
    }
 | 
			
		||||
    MinTime = Time(0, 1)
 | 
			
		||||
 | 
			
		||||
    VText = ["text"]
 | 
			
		||||
    # OutputFormats = list(Exporters) + VText
 | 
			
		||||
    # Default format must be the first, list(Exporters) has random order
 | 
			
		||||
    OutputFormats = ["xml", "fdsnxml", "stationxml", "sc3ml"] + VText
 | 
			
		||||
 | 
			
		||||
    PLevel = ["level"]
 | 
			
		||||
    PIncludeRestricted = ["includerestricted"]
 | 
			
		||||
    PIncludeAvailability = ["includeavailability"]
 | 
			
		||||
    PUpdateAfter = ["updateafter"]
 | 
			
		||||
    PMatchTimeSeries = ["matchtimeseries"]
 | 
			
		||||
 | 
			
		||||
    # non standard parameters
 | 
			
		||||
    PFormatted = ["formatted"]
 | 
			
		||||
 | 
			
		||||
    POSTParams = (
 | 
			
		||||
        RequestOptions.POSTParams
 | 
			
		||||
        + RequestOptions.GeoParams
 | 
			
		||||
        + PLevel
 | 
			
		||||
        + PIncludeRestricted
 | 
			
		||||
        + PIncludeAvailability
 | 
			
		||||
        + PUpdateAfter
 | 
			
		||||
        + PMatchTimeSeries
 | 
			
		||||
        + PFormatted
 | 
			
		||||
    )
 | 
			
		||||
    GETParams = RequestOptions.GETParams + RequestOptions.WindowTimeParams + POSTParams
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
 | 
			
		||||
        self.service = "fdsnws-station"
 | 
			
		||||
 | 
			
		||||
        self.includeSta = True
 | 
			
		||||
        self.includeCha = False
 | 
			
		||||
        self.includeRes = False
 | 
			
		||||
 | 
			
		||||
        self.restricted = None
 | 
			
		||||
        self.availability = None
 | 
			
		||||
        self.updatedAfter = None
 | 
			
		||||
        self.matchTimeSeries = None
 | 
			
		||||
 | 
			
		||||
        # non standard parameters
 | 
			
		||||
        self.formatted = None
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def parse(self):
 | 
			
		||||
        self.parseTime(True)
 | 
			
		||||
        self.parseChannel()
 | 
			
		||||
        self.parseGeo()
 | 
			
		||||
        self.parseOutput()
 | 
			
		||||
 | 
			
		||||
        # level: [network, station, channel, response]
 | 
			
		||||
        key, value = self.getFirstValue(self.PLevel)
 | 
			
		||||
        if value is not None:
 | 
			
		||||
            value = value.lower()
 | 
			
		||||
            if value in ("network", "net"):
 | 
			
		||||
                self.includeSta = False
 | 
			
		||||
            elif value in ("channel", "cha", "chan"):
 | 
			
		||||
                self.includeCha = True
 | 
			
		||||
            elif value in ("response", "res", "resp"):
 | 
			
		||||
                self.includeCha = True
 | 
			
		||||
                self.includeRes = True
 | 
			
		||||
            elif value not in ("station", "sta"):
 | 
			
		||||
                self.raiseValueError(key)
 | 
			
		||||
 | 
			
		||||
        # includeRestricted (optional)
 | 
			
		||||
        self.restricted = self.parseBool(self.PIncludeRestricted)
 | 
			
		||||
 | 
			
		||||
        # includeAvailability (optionalsc3ml)
 | 
			
		||||
        self.availability = self.parseBool(self.PIncludeAvailability)
 | 
			
		||||
 | 
			
		||||
        # updatedAfter (optional), currently not supported
 | 
			
		||||
        self.updatedAfter = self.parseTimeStr(self.PUpdateAfter)
 | 
			
		||||
 | 
			
		||||
        # includeAvailability (optional)
 | 
			
		||||
        self.matchTimeSeries = self.parseBool(self.PMatchTimeSeries)
 | 
			
		||||
 | 
			
		||||
        # format XML
 | 
			
		||||
        self.formatted = self.parseBool(self.PFormatted)
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def networkIter(self, inv, matchTime=False):
 | 
			
		||||
        for i in range(inv.networkCount()):
 | 
			
		||||
            net = inv.network(i)
 | 
			
		||||
 | 
			
		||||
            for ro in self.streams:
 | 
			
		||||
                # network code
 | 
			
		||||
                if ro.channel and not ro.channel.matchNet(net.code()):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                # start and end time
 | 
			
		||||
                if matchTime and ro.time:
 | 
			
		||||
                    try:
 | 
			
		||||
                        end = net.end()
 | 
			
		||||
                    except ValueError:
 | 
			
		||||
                        end = None
 | 
			
		||||
                    if not ro.time.match(net.start(), end):
 | 
			
		||||
                        continue
 | 
			
		||||
 | 
			
		||||
                yield net
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def stationIter(self, net, matchTime=False):
 | 
			
		||||
        for i in range(net.stationCount()):
 | 
			
		||||
            sta = net.station(i)
 | 
			
		||||
 | 
			
		||||
            # geographic location
 | 
			
		||||
            if self.geo:
 | 
			
		||||
                try:
 | 
			
		||||
                    lat = sta.latitude()
 | 
			
		||||
                    lon = sta.longitude()
 | 
			
		||||
                except ValueError:
 | 
			
		||||
                    continue
 | 
			
		||||
                if not self.geo.match(lat, lon):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
            for ro in self.streams:
 | 
			
		||||
                # station code
 | 
			
		||||
                if ro.channel and (
 | 
			
		||||
                    not ro.channel.matchSta(sta.code())
 | 
			
		||||
                    or not ro.channel.matchNet(net.code())
 | 
			
		||||
                ):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                # start and end time
 | 
			
		||||
                if matchTime and ro.time:
 | 
			
		||||
                    try:
 | 
			
		||||
                        end = sta.end()
 | 
			
		||||
                    except ValueError:
 | 
			
		||||
                        end = None
 | 
			
		||||
                    if not ro.time.match(sta.start(), end):
 | 
			
		||||
                        continue
 | 
			
		||||
 | 
			
		||||
                yield sta
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def locationIter(self, net, sta, matchTime=False):
 | 
			
		||||
        for i in range(sta.sensorLocationCount()):
 | 
			
		||||
            loc = sta.sensorLocation(i)
 | 
			
		||||
 | 
			
		||||
            for ro in self.streams:
 | 
			
		||||
                # location code
 | 
			
		||||
                if ro.channel and (
 | 
			
		||||
                    not ro.channel.matchLoc(loc.code())
 | 
			
		||||
                    or not ro.channel.matchSta(sta.code())
 | 
			
		||||
                    or not ro.channel.matchNet(net.code())
 | 
			
		||||
                ):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                # start and end time
 | 
			
		||||
                if matchTime and ro.time:
 | 
			
		||||
                    try:
 | 
			
		||||
                        end = loc.end()
 | 
			
		||||
                    except ValueError:
 | 
			
		||||
                        end = None
 | 
			
		||||
                    if not ro.time.match(loc.start(), end):
 | 
			
		||||
                        continue
 | 
			
		||||
 | 
			
		||||
                yield loc
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def streamIter(self, net, sta, loc, matchTime, dac):
 | 
			
		||||
        for i in range(loc.streamCount()):
 | 
			
		||||
            stream = loc.stream(i)
 | 
			
		||||
 | 
			
		||||
            for ro in self.streams:
 | 
			
		||||
                # stream code
 | 
			
		||||
                if ro.channel and (
 | 
			
		||||
                    not ro.channel.matchCha(stream.code())
 | 
			
		||||
                    or not ro.channel.matchLoc(loc.code())
 | 
			
		||||
                    or not ro.channel.matchSta(sta.code())
 | 
			
		||||
                    or not ro.channel.matchNet(net.code())
 | 
			
		||||
                ):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                # start and end time
 | 
			
		||||
                if matchTime and ro.time:
 | 
			
		||||
                    try:
 | 
			
		||||
                        end = stream.end()
 | 
			
		||||
                    except ValueError:
 | 
			
		||||
                        end = None
 | 
			
		||||
                    if not ro.time.match(stream.start(), end):
 | 
			
		||||
                        continue
 | 
			
		||||
 | 
			
		||||
                # match data availability extent
 | 
			
		||||
                if dac is not None and self.matchTimeSeries:
 | 
			
		||||
                    extent = dac.extent(
 | 
			
		||||
                        net.code(), sta.code(), loc.code(), stream.code()
 | 
			
		||||
                    )
 | 
			
		||||
                    if extent is None or (
 | 
			
		||||
                        ro.time and not ro.time.match(extent.start(), extent.end())
 | 
			
		||||
                    ):
 | 
			
		||||
                        continue
 | 
			
		||||
 | 
			
		||||
                yield stream
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
class FDSNStation(BaseResource):
 | 
			
		||||
    isLeaf = True
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        inv,
 | 
			
		||||
        restricted,
 | 
			
		||||
        maxObj,
 | 
			
		||||
        daEnabled,
 | 
			
		||||
        conditionalRequestsEnabled,
 | 
			
		||||
        timeInventoryLoaded,
 | 
			
		||||
    ):
 | 
			
		||||
        super().__init__(VERSION)
 | 
			
		||||
 | 
			
		||||
        self._inv = inv
 | 
			
		||||
        self._allowRestricted = restricted
 | 
			
		||||
        self._maxObj = maxObj
 | 
			
		||||
        self._daEnabled = daEnabled
 | 
			
		||||
        self._conditionalRequestsEnabled = conditionalRequestsEnabled
 | 
			
		||||
        self._timeInventoryLoaded = timeInventoryLoaded.seconds()
 | 
			
		||||
 | 
			
		||||
        # additional object count dependent on detail level
 | 
			
		||||
        self._resLevelCount = (
 | 
			
		||||
            inv.responsePAZCount()
 | 
			
		||||
            + inv.responseFIRCount()
 | 
			
		||||
            + inv.responsePolynomialCount()
 | 
			
		||||
            + inv.responseIIRCount()
 | 
			
		||||
            + inv.responseFAPCount()
 | 
			
		||||
        )
 | 
			
		||||
        for i in range(inv.dataloggerCount()):
 | 
			
		||||
            self._resLevelCount += inv.datalogger(i).decimationCount()
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def render_OPTIONS(self, req):
 | 
			
		||||
        req.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
 | 
			
		||||
        req.setHeader(
 | 
			
		||||
            "Access-Control-Allow-Headers",
 | 
			
		||||
            "Accept, Content-Type, X-Requested-With, Origin",
 | 
			
		||||
        )
 | 
			
		||||
        req.setHeader("Content-Type", "text/plain; charset=utf-8")
 | 
			
		||||
        return ""
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def render_GET(self, req):
 | 
			
		||||
        # Parse and validate GET parameters
 | 
			
		||||
        ro = _StationRequestOptions()
 | 
			
		||||
        try:
 | 
			
		||||
            ro.parseGET(req.args)
 | 
			
		||||
            ro.parse()
 | 
			
		||||
            # the GET operation supports exactly one stream filter
 | 
			
		||||
            ro.streams.append(ro)
 | 
			
		||||
        except ValueError as e:
 | 
			
		||||
            seiscomp.logging.warning(str(e))
 | 
			
		||||
            return self.renderErrorPage(req, http.BAD_REQUEST, str(e), ro)
 | 
			
		||||
 | 
			
		||||
        return self._prepareRequest(req, ro)
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def render_POST(self, req):
 | 
			
		||||
        # Parse and validate POST parameters
 | 
			
		||||
        ro = _StationRequestOptions()
 | 
			
		||||
        try:
 | 
			
		||||
            ro.parsePOST(req.content)
 | 
			
		||||
            ro.parse()
 | 
			
		||||
        except ValueError as e:
 | 
			
		||||
            seiscomp.logging.warning(str(e))
 | 
			
		||||
            return self.renderErrorPage(req, http.BAD_REQUEST, str(e), ro)
 | 
			
		||||
 | 
			
		||||
        return self._prepareRequest(req, ro)
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def _prepareRequest(self, req, ro):
 | 
			
		||||
        if ro.availability and not self._daEnabled:
 | 
			
		||||
            msg = "including of availability information not supported"
 | 
			
		||||
            return self.renderErrorPage(req, http.BAD_REQUEST, msg, ro)
 | 
			
		||||
 | 
			
		||||
        if ro.updatedAfter:
 | 
			
		||||
            msg = "filtering based on update time not supported"
 | 
			
		||||
            return self.renderErrorPage(req, http.BAD_REQUEST, msg, ro)
 | 
			
		||||
 | 
			
		||||
        if ro.matchTimeSeries and not self._daEnabled:
 | 
			
		||||
            msg = "filtering based on available time series not supported"
 | 
			
		||||
            return self.renderErrorPage(req, http.BAD_REQUEST, msg, ro)
 | 
			
		||||
 | 
			
		||||
        # load data availability if requested
 | 
			
		||||
        dac = None
 | 
			
		||||
        if ro.availability or ro.matchTimeSeries:
 | 
			
		||||
            dac = Application.Instance().getDACache()
 | 
			
		||||
            if dac is None or len(dac.extents()) == 0:
 | 
			
		||||
                msg = "no data availabiltiy extent information found"
 | 
			
		||||
                return self.renderErrorPage(req, http.NO_CONTENT, msg, ro)
 | 
			
		||||
 | 
			
		||||
        # Exporter, 'None' is used for text output
 | 
			
		||||
        if ro.format in ro.VText:
 | 
			
		||||
            if ro.includeRes:
 | 
			
		||||
                msg = "response level output not available in text format"
 | 
			
		||||
                return self.renderErrorPage(req, http.BAD_REQUEST, msg, ro)
 | 
			
		||||
            req.setHeader("Content-Type", "text/plain; charset=utf-8")
 | 
			
		||||
            d = deferToThread(self._processRequestText, req, ro, dac)
 | 
			
		||||
        else:
 | 
			
		||||
            exp = Exporter.Create(ro.Exporters[ro.format])
 | 
			
		||||
            if exp is None:
 | 
			
		||||
                msg = (
 | 
			
		||||
                    f"output format '{ro.format}' no available, export module "
 | 
			
		||||
                    f"'{ro.Exporters[ro.format]}' could not be loaded."
 | 
			
		||||
                )
 | 
			
		||||
                return self.renderErrorPage(req, http.BAD_REQUEST, msg, ro)
 | 
			
		||||
 | 
			
		||||
            req.setHeader("Content-Type", "application/xml; charset=utf-8")
 | 
			
		||||
            exp.setFormattedOutput(bool(ro.formatted))
 | 
			
		||||
            d = deferToThread(self._processRequestExp, req, ro, exp, dac)
 | 
			
		||||
 | 
			
		||||
        req.notifyFinish().addErrback(utils.onCancel, d)
 | 
			
		||||
        d.addBoth(utils.onFinish, req)
 | 
			
		||||
 | 
			
		||||
        # The request is handled by the deferred object
 | 
			
		||||
        return server.NOT_DONE_YET
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def _processRequestExp(self, req, ro, exp, dac):
 | 
			
		||||
        if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        staCount, locCount, chaCount, extCount, objCount = 0, 0, 0, 0, 0
 | 
			
		||||
 | 
			
		||||
        seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
 | 
			
		||||
        newInv = seiscomp.datamodel.Inventory()
 | 
			
		||||
        dataloggers, sensors, extents = set(), set(), {}
 | 
			
		||||
 | 
			
		||||
        skipRestricted = not self._allowRestricted or (
 | 
			
		||||
            ro.restricted is not None and not ro.restricted
 | 
			
		||||
        )
 | 
			
		||||
        levelNet = not ro.includeSta
 | 
			
		||||
        levelSta = ro.includeSta and not ro.includeCha
 | 
			
		||||
 | 
			
		||||
        isConditionalRequest = self._isConditionalRequest(req)
 | 
			
		||||
 | 
			
		||||
        # iterate over inventory networks
 | 
			
		||||
        for net in ro.networkIter(self._inv, levelNet):
 | 
			
		||||
            if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
                return False
 | 
			
		||||
            if skipRestricted and utils.isRestricted(net):
 | 
			
		||||
                continue
 | 
			
		||||
            newNet = seiscomp.datamodel.Network(net)
 | 
			
		||||
 | 
			
		||||
            # Copy comments
 | 
			
		||||
            for i in range(net.commentCount()):
 | 
			
		||||
                newNet.add(seiscomp.datamodel.Comment(net.comment(i)))
 | 
			
		||||
 | 
			
		||||
            # iterate over inventory stations of current network
 | 
			
		||||
            for sta in ro.stationIter(net, levelSta):
 | 
			
		||||
                if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
                    return False
 | 
			
		||||
                if skipRestricted and utils.isRestricted(sta):
 | 
			
		||||
                    continue
 | 
			
		||||
                if not self.checkObjects(req, objCount, self._maxObj):
 | 
			
		||||
                    return False
 | 
			
		||||
 | 
			
		||||
                if ro.includeCha:
 | 
			
		||||
                    numCha, numLoc, d, s, e = self._processStation(
 | 
			
		||||
                        newNet, net, sta, ro, dac, skipRestricted, isConditionalRequest
 | 
			
		||||
                    )
 | 
			
		||||
                    if numCha > 0:
 | 
			
		||||
                        if isConditionalRequest:
 | 
			
		||||
                            self.returnNotModified(req, ro)
 | 
			
		||||
                            return True
 | 
			
		||||
                        locCount += numLoc
 | 
			
		||||
                        chaCount += numCha
 | 
			
		||||
                        extCount += len(e)
 | 
			
		||||
                        objCount += numLoc + numCha + extCount
 | 
			
		||||
                        if not self.checkObjects(req, objCount, self._maxObj):
 | 
			
		||||
                            return False
 | 
			
		||||
                        dataloggers |= d
 | 
			
		||||
                        sensors |= s
 | 
			
		||||
                        for k, v in e.items():
 | 
			
		||||
                            if k not in extents:
 | 
			
		||||
                                extents[k] = v
 | 
			
		||||
                elif self._matchStation(net, sta, ro, dac):
 | 
			
		||||
                    if isConditionalRequest:
 | 
			
		||||
                        self.returnNotModified(req, ro)
 | 
			
		||||
                        return True
 | 
			
		||||
                    if ro.includeSta:
 | 
			
		||||
                        newSta = seiscomp.datamodel.Station(sta)
 | 
			
		||||
                        # Copy comments
 | 
			
		||||
                        for i in range(sta.commentCount()):
 | 
			
		||||
                            newSta.add(seiscomp.datamodel.Comment(sta.comment(i)))
 | 
			
		||||
                        newNet.add(newSta)
 | 
			
		||||
                    else:
 | 
			
		||||
                        # no station output requested: one matching station
 | 
			
		||||
                        # is sufficient to include the network
 | 
			
		||||
                        newInv.add(newNet)
 | 
			
		||||
                        objCount += 1
 | 
			
		||||
                        break
 | 
			
		||||
 | 
			
		||||
            if newNet.stationCount() > 0:
 | 
			
		||||
                newInv.add(newNet)
 | 
			
		||||
                staCount += newNet.stationCount()
 | 
			
		||||
                objCount += staCount + 1
 | 
			
		||||
 | 
			
		||||
        # Return 204 if no matching inventory was found
 | 
			
		||||
        if newInv.networkCount() == 0:
 | 
			
		||||
            msg = "no matching inventory found"
 | 
			
		||||
            self.writeErrorPage(req, http.NO_CONTENT, msg, ro)
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        if self._conditionalRequestsEnabled:
 | 
			
		||||
            req.setHeader(
 | 
			
		||||
                "Last-Modified", http.datetimeToString(self._timeInventoryLoaded)
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        # Copy references (dataloggers, responses, sensors)
 | 
			
		||||
        decCount, resCount = 0, 0
 | 
			
		||||
        if ro.includeCha:
 | 
			
		||||
            decCount = self._copyReferences(
 | 
			
		||||
                newInv, req, objCount, self._inv, ro, dataloggers, sensors, self._maxObj
 | 
			
		||||
            )
 | 
			
		||||
            if decCount is None:
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
            resCount = (
 | 
			
		||||
                newInv.responsePAZCount()
 | 
			
		||||
                + newInv.responseFIRCount()
 | 
			
		||||
                + newInv.responsePolynomialCount()
 | 
			
		||||
                + newInv.responseFAPCount()
 | 
			
		||||
                + newInv.responseIIRCount()
 | 
			
		||||
            )
 | 
			
		||||
            objCount += (
 | 
			
		||||
                resCount + decCount + newInv.dataloggerCount() + newInv.sensorCount()
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        # Copy data extents
 | 
			
		||||
        objOut = newInv
 | 
			
		||||
        if len(extents) > 0:
 | 
			
		||||
            objCount += 1
 | 
			
		||||
            da = seiscomp.datamodel.DataAvailability()
 | 
			
		||||
            for k, v in extents.items():
 | 
			
		||||
                objCount += 1
 | 
			
		||||
                da.add(seiscomp.datamodel.DataExtent(v))
 | 
			
		||||
            objOut = ExportObjectList()
 | 
			
		||||
            objOut.append(newInv)
 | 
			
		||||
            objOut.append(da)
 | 
			
		||||
 | 
			
		||||
        sink = utils.Sink(req)
 | 
			
		||||
        if not exp.write(sink, objOut):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        seiscomp.logging.debug(
 | 
			
		||||
            f"{ro.service}: returned {newInv.networkCount()}Net, {staCount}Sta, "
 | 
			
		||||
            f"{locCount}Loc, {chaCount}Cha, {newInv.dataloggerCount()}DL, "
 | 
			
		||||
            f"{decCount}Dec, {newInv.sensorCount()}Sen, {resCount}Res, {extCount}DAExt "
 | 
			
		||||
            f"(total objects/bytes: {objCount}/{sink.written})"
 | 
			
		||||
        )
 | 
			
		||||
        utils.accessLog(req, ro, http.OK, sink.written, None)
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _formatEpoch(obj):
 | 
			
		||||
        df = "%FT%T"
 | 
			
		||||
        dfMS = "%FT%T.%f"
 | 
			
		||||
 | 
			
		||||
        if obj.start().microseconds() > 0:
 | 
			
		||||
            start = obj.start().toString(dfMS)
 | 
			
		||||
        else:
 | 
			
		||||
            start = obj.start().toString(df)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            if obj.end().microseconds() > 0:
 | 
			
		||||
                end = obj.end().toString(dfMS)
 | 
			
		||||
            else:
 | 
			
		||||
                end = obj.end().toString(df)
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            end = ""
 | 
			
		||||
 | 
			
		||||
        return start, end
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def _processRequestText(self, req, ro, dac):
 | 
			
		||||
        if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        skipRestricted = not self._allowRestricted or (
 | 
			
		||||
            ro.restricted is not None and not ro.restricted
 | 
			
		||||
        )
 | 
			
		||||
        isConditionalRequest = self._isConditionalRequest(req)
 | 
			
		||||
 | 
			
		||||
        data = ""
 | 
			
		||||
        lines = []
 | 
			
		||||
 | 
			
		||||
        # level = network
 | 
			
		||||
        if not ro.includeSta:
 | 
			
		||||
            data = "#Network|Description|StartTime|EndTime|TotalStations\n"
 | 
			
		||||
 | 
			
		||||
            # iterate over inventory networks
 | 
			
		||||
            for net in ro.networkIter(self._inv, True):
 | 
			
		||||
                if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
                    return False
 | 
			
		||||
                if skipRestricted and utils.isRestricted(net):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                # at least one matching station is required
 | 
			
		||||
                stationFound = False
 | 
			
		||||
                for sta in ro.stationIter(net, False):
 | 
			
		||||
                    if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
                        return False
 | 
			
		||||
                    if self._matchStation(net, sta, ro, dac) and not (
 | 
			
		||||
                        skipRestricted and utils.isRestricted(sta)
 | 
			
		||||
                    ):
 | 
			
		||||
                        stationFound = True
 | 
			
		||||
                        break
 | 
			
		||||
                if not stationFound:
 | 
			
		||||
                    continue
 | 
			
		||||
                if isConditionalRequest:
 | 
			
		||||
                    self.returnNotModified(req, ro)
 | 
			
		||||
                    return True
 | 
			
		||||
 | 
			
		||||
                start, end = self._formatEpoch(net)
 | 
			
		||||
                lines.append(
 | 
			
		||||
                    (
 | 
			
		||||
                        f"{net.code()} {start}",
 | 
			
		||||
                        f"{net.code()}|{net.description()}|{start}|{end}|"
 | 
			
		||||
                        f"{net.stationCount()}\n",
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
        # level = station
 | 
			
		||||
        elif not ro.includeCha:
 | 
			
		||||
            data = (
 | 
			
		||||
                "#Network|Station|Latitude|Longitude|Elevation|"
 | 
			
		||||
                "SiteName|StartTime|EndTime\n"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # iterate over inventory networks
 | 
			
		||||
            for net in ro.networkIter(self._inv, False):
 | 
			
		||||
                if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
                    return False
 | 
			
		||||
                if skipRestricted and utils.isRestricted(net):
 | 
			
		||||
                    continue
 | 
			
		||||
                # iterate over inventory stations
 | 
			
		||||
                for sta in ro.stationIter(net, True):
 | 
			
		||||
                    if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
                        return False
 | 
			
		||||
                    if not self._matchStation(net, sta, ro, dac) or (
 | 
			
		||||
                        skipRestricted and utils.isRestricted(sta)
 | 
			
		||||
                    ):
 | 
			
		||||
                        continue
 | 
			
		||||
                    if isConditionalRequest:
 | 
			
		||||
                        self.returnNotModified(req, ro)
 | 
			
		||||
                        return True
 | 
			
		||||
 | 
			
		||||
                    try:
 | 
			
		||||
                        lat = str(sta.latitude())
 | 
			
		||||
                    except ValueError:
 | 
			
		||||
                        lat = ""
 | 
			
		||||
                    try:
 | 
			
		||||
                        lon = str(sta.longitude())
 | 
			
		||||
                    except ValueError:
 | 
			
		||||
                        lon = ""
 | 
			
		||||
                    try:
 | 
			
		||||
                        elev = str(sta.elevation())
 | 
			
		||||
                    except ValueError:
 | 
			
		||||
                        elev = ""
 | 
			
		||||
                    try:
 | 
			
		||||
                        desc = sta.description()
 | 
			
		||||
                    except ValueError:
 | 
			
		||||
                        desc = ""
 | 
			
		||||
 | 
			
		||||
                    start, end = self._formatEpoch(sta)
 | 
			
		||||
                    lines.append(
 | 
			
		||||
                        (
 | 
			
		||||
                            f"{net.code()}.{sta.code()} {start}",
 | 
			
		||||
                            f"{net.code()}|{sta.code()}|{lat}|{lon}|{elev}|{desc}|"
 | 
			
		||||
                            f"{start}|{end}\n",
 | 
			
		||||
                        )
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
        # level = channel (resonse level not supported in text format)
 | 
			
		||||
        else:
 | 
			
		||||
            data = (
 | 
			
		||||
                "#Network|Station|Location|Channel|Latitude|Longitude|"
 | 
			
		||||
                "Elevation|Depth|Azimuth|Dip|SensorDescription|Scale|"
 | 
			
		||||
                "ScaleFreq|ScaleUnits|SampleRate|StartTime|EndTime\n"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # iterate over inventory networks
 | 
			
		||||
            for net in ro.networkIter(self._inv, False):
 | 
			
		||||
                if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
                    return False
 | 
			
		||||
                if skipRestricted and utils.isRestricted(net):
 | 
			
		||||
                    continue
 | 
			
		||||
                # iterate over inventory stations, locations, streams
 | 
			
		||||
                for sta in ro.stationIter(net, False):
 | 
			
		||||
                    if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
                        return False
 | 
			
		||||
                    if skipRestricted and utils.isRestricted(sta):
 | 
			
		||||
                        continue
 | 
			
		||||
                    for loc in ro.locationIter(net, sta, True):
 | 
			
		||||
                        for stream in ro.streamIter(net, sta, loc, True, dac):
 | 
			
		||||
                            if skipRestricted and utils.isRestricted(stream):
 | 
			
		||||
                                continue
 | 
			
		||||
                            if isConditionalRequest:
 | 
			
		||||
                                self.returnNotModified(req, ro)
 | 
			
		||||
                                return True
 | 
			
		||||
 | 
			
		||||
                            try:
 | 
			
		||||
                                lat = str(loc.latitude())
 | 
			
		||||
                            except ValueError:
 | 
			
		||||
                                lat = ""
 | 
			
		||||
                            try:
 | 
			
		||||
                                lon = str(loc.longitude())
 | 
			
		||||
                            except ValueError:
 | 
			
		||||
                                lon = ""
 | 
			
		||||
                            try:
 | 
			
		||||
                                elev = str(loc.elevation())
 | 
			
		||||
                            except ValueError:
 | 
			
		||||
                                elev = ""
 | 
			
		||||
                            try:
 | 
			
		||||
                                depth = str(stream.depth())
 | 
			
		||||
                            except ValueError:
 | 
			
		||||
                                depth = ""
 | 
			
		||||
                            try:
 | 
			
		||||
                                azi = str(stream.azimuth())
 | 
			
		||||
                            except ValueError:
 | 
			
		||||
                                azi = ""
 | 
			
		||||
                            try:
 | 
			
		||||
                                dip = str(stream.dip())
 | 
			
		||||
                            except ValueError:
 | 
			
		||||
                                dip = ""
 | 
			
		||||
 | 
			
		||||
                            desc = ""
 | 
			
		||||
                            try:
 | 
			
		||||
                                sensor = self._inv.findSensor(stream.sensor())
 | 
			
		||||
                                if sensor is not None:
 | 
			
		||||
                                    desc = sensor.description()
 | 
			
		||||
                            except ValueError:
 | 
			
		||||
                                pass
 | 
			
		||||
 | 
			
		||||
                            try:
 | 
			
		||||
                                scale = str(stream.gain())
 | 
			
		||||
                            except ValueError:
 | 
			
		||||
                                scale = ""
 | 
			
		||||
                            try:
 | 
			
		||||
                                scaleFreq = str(stream.gainFrequency())
 | 
			
		||||
                            except ValueError:
 | 
			
		||||
                                scaleFreq = ""
 | 
			
		||||
                            try:
 | 
			
		||||
                                scaleUnit = str(stream.gainUnit())
 | 
			
		||||
                            except ValueError:
 | 
			
		||||
                                scaleUnit = ""
 | 
			
		||||
                            try:
 | 
			
		||||
                                sr = str(
 | 
			
		||||
                                    stream.sampleRateNumerator()
 | 
			
		||||
                                    / stream.sampleRateDenominator()
 | 
			
		||||
                                )
 | 
			
		||||
                            except (ValueError, ZeroDivisionError):
 | 
			
		||||
                                sr = ""
 | 
			
		||||
 | 
			
		||||
                            start, end = self._formatEpoch(stream)
 | 
			
		||||
                            lines.append(
 | 
			
		||||
                                (
 | 
			
		||||
                                    f"{net.code()}.{sta.code()}.{loc.code()}."
 | 
			
		||||
                                    f"{stream.code()} {start}",
 | 
			
		||||
                                    f"{net.code()}|{sta.code()}|{loc.code()}|"
 | 
			
		||||
                                    f"{stream.code()}|{lat}|{lon}|{elev}|{depth}|{azi}|"
 | 
			
		||||
                                    f"{dip}|{desc}|{scale}|{scaleFreq}|{scaleUnit}|"
 | 
			
		||||
                                    f"{sr}|{start}|{end}\n",
 | 
			
		||||
                                )
 | 
			
		||||
                            )
 | 
			
		||||
 | 
			
		||||
        # sort lines and append to final data string
 | 
			
		||||
        lines.sort(key=lambda line: line[0])
 | 
			
		||||
        for line in lines:
 | 
			
		||||
            data += line[1]
 | 
			
		||||
 | 
			
		||||
        # Return 204 if no matching inventory was found
 | 
			
		||||
        if len(lines) == 0:
 | 
			
		||||
            msg = "no matching inventory found"
 | 
			
		||||
            self.writeErrorPage(req, http.NO_CONTENT, msg, ro)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if self._conditionalRequestsEnabled:
 | 
			
		||||
            req.setHeader(
 | 
			
		||||
                "Last-Modified", http.datetimeToString(self._timeInventoryLoaded)
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        dataBin = utils.b_str(data)
 | 
			
		||||
        utils.writeTSBin(req, dataBin)
 | 
			
		||||
        seiscomp.logging.debug(
 | 
			
		||||
            f"{ro.service}: returned {len(lines)} lines (total bytes: {len(dataBin)})"
 | 
			
		||||
        )
 | 
			
		||||
        utils.accessLog(req, ro, http.OK, len(dataBin), None)
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    def _isConditionalRequest(self, req):
 | 
			
		||||
        # support for time based conditional requests
 | 
			
		||||
        if not self._conditionalRequestsEnabled:
 | 
			
		||||
            return False
 | 
			
		||||
        if req.method not in (b"GET", b"HEAD"):
 | 
			
		||||
            return False
 | 
			
		||||
        if req.getHeader("If-None-Match") is not None:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        modifiedSince = req.getHeader("If-Modified-Since")
 | 
			
		||||
        if not modifiedSince:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        modifiedSince = utils.stringToDatetime(modifiedSince)
 | 
			
		||||
        return modifiedSince and self._timeInventoryLoaded <= modifiedSince
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    # Checks if at least one location and channel combination matches the
 | 
			
		||||
    # request options
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _matchStation(net, sta, ro, dac):
 | 
			
		||||
        # No filter: return true immediately
 | 
			
		||||
        if dac is None and (
 | 
			
		||||
            not ro.channel or (not ro.channel.loc and not ro.channel.cha)
 | 
			
		||||
        ):
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        for loc in ro.locationIter(net, sta, False):
 | 
			
		||||
            if dac is None and not ro.channel.cha and not ro.time:
 | 
			
		||||
                return True
 | 
			
		||||
 | 
			
		||||
            for _ in ro.streamIter(net, sta, loc, False, dac):
 | 
			
		||||
                return True
 | 
			
		||||
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    # Adds a deep copy of the specified station to the new network if the
 | 
			
		||||
    # location and channel combination matches the request options (if any)
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _processStation(
 | 
			
		||||
        newNet, net, sta, ro, dac, skipRestricted, isConditionalRequest
 | 
			
		||||
    ):
 | 
			
		||||
        chaCount = 0
 | 
			
		||||
        dataloggers, sensors, extents = set(), set(), {}
 | 
			
		||||
        newSta = seiscomp.datamodel.Station(sta)
 | 
			
		||||
        includeAvailability = dac is not None and ro.availability
 | 
			
		||||
 | 
			
		||||
        # Copy comments
 | 
			
		||||
        for i in range(sta.commentCount()):
 | 
			
		||||
            newSta.add(seiscomp.datamodel.Comment(sta.comment(i)))
 | 
			
		||||
 | 
			
		||||
        for loc in ro.locationIter(net, sta, True):
 | 
			
		||||
            newLoc = seiscomp.datamodel.SensorLocation(loc)
 | 
			
		||||
            # Copy comments
 | 
			
		||||
            for i in range(loc.commentCount()):
 | 
			
		||||
                newLoc.add(seiscomp.datamodel.Comment(loc.comment(i)))
 | 
			
		||||
 | 
			
		||||
            for stream in ro.streamIter(net, sta, loc, True, dac):
 | 
			
		||||
                if skipRestricted and utils.isRestricted(stream):
 | 
			
		||||
                    continue
 | 
			
		||||
                if isConditionalRequest:
 | 
			
		||||
                    return 1, 1, [], [], []
 | 
			
		||||
                newCha = seiscomp.datamodel.Stream(stream)
 | 
			
		||||
                # Copy comments
 | 
			
		||||
                for i in range(stream.commentCount()):
 | 
			
		||||
                    newCha.add(seiscomp.datamodel.Comment(stream.comment(i)))
 | 
			
		||||
                newLoc.add(newCha)
 | 
			
		||||
                dataloggers.add(stream.datalogger())
 | 
			
		||||
                sensors.add(stream.sensor())
 | 
			
		||||
                if includeAvailability:
 | 
			
		||||
                    ext = dac.extent(net.code(), sta.code(), loc.code(), stream.code())
 | 
			
		||||
                    if ext is not None and ext.publicID() not in extents:
 | 
			
		||||
                        extents[ext.publicID()] = ext
 | 
			
		||||
 | 
			
		||||
            if newLoc.streamCount() > 0:
 | 
			
		||||
                newSta.add(newLoc)
 | 
			
		||||
                chaCount += newLoc.streamCount()
 | 
			
		||||
 | 
			
		||||
        if newSta.sensorLocationCount() > 0:
 | 
			
		||||
            newNet.add(newSta)
 | 
			
		||||
            return chaCount, newSta.sensorLocationCount(), dataloggers, sensors, extents
 | 
			
		||||
 | 
			
		||||
        return 0, 0, [], [], []
 | 
			
		||||
 | 
			
		||||
    # ---------------------------------------------------------------------------
 | 
			
		||||
    # Copy references (data loggers, sensors, responses) depended on request
 | 
			
		||||
    # options
 | 
			
		||||
    def _copyReferences(
 | 
			
		||||
        self, newInv, req, objCount, inv, ro, dataloggers, sensors, maxObj
 | 
			
		||||
    ):
 | 
			
		||||
        responses = set()
 | 
			
		||||
        decCount = 0
 | 
			
		||||
 | 
			
		||||
        # datalogger
 | 
			
		||||
        for i in range(inv.dataloggerCount()):
 | 
			
		||||
            if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
                return None
 | 
			
		||||
            logger = inv.datalogger(i)
 | 
			
		||||
            if logger.publicID() not in dataloggers:
 | 
			
		||||
                continue
 | 
			
		||||
            newLogger = seiscomp.datamodel.Datalogger(logger)
 | 
			
		||||
            newInv.add(newLogger)
 | 
			
		||||
            # decimations are only needed for responses
 | 
			
		||||
            if ro.includeRes:
 | 
			
		||||
                for j in range(logger.decimationCount()):
 | 
			
		||||
                    decimation = logger.decimation(j)
 | 
			
		||||
                    newLogger.add(seiscomp.datamodel.Decimation(decimation))
 | 
			
		||||
 | 
			
		||||
                    # collect response ids
 | 
			
		||||
                    filterStr = ""
 | 
			
		||||
                    try:
 | 
			
		||||
                        filterStr = f"{decimation.analogueFilterChain().content()} "
 | 
			
		||||
                    except ValueError:
 | 
			
		||||
                        pass
 | 
			
		||||
                    try:
 | 
			
		||||
                        filterStr += decimation.digitalFilterChain().content()
 | 
			
		||||
                    except ValueError:
 | 
			
		||||
                        pass
 | 
			
		||||
                    for resp in filterStr.split():
 | 
			
		||||
                        responses.add(resp)
 | 
			
		||||
                decCount += newLogger.decimationCount()
 | 
			
		||||
 | 
			
		||||
        objCount += newInv.dataloggerCount() + decCount
 | 
			
		||||
        resCount = len(responses)
 | 
			
		||||
        if not self.checkObjects(req, objCount + resCount, maxObj):
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        # sensor
 | 
			
		||||
        for i in range(inv.sensorCount()):
 | 
			
		||||
            if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
                return None
 | 
			
		||||
            sensor = inv.sensor(i)
 | 
			
		||||
            if sensor.publicID() not in sensors:
 | 
			
		||||
                continue
 | 
			
		||||
            newSensor = seiscomp.datamodel.Sensor(sensor)
 | 
			
		||||
            newInv.add(newSensor)
 | 
			
		||||
            resp = newSensor.response()
 | 
			
		||||
            if resp:
 | 
			
		||||
                if ro.includeRes:
 | 
			
		||||
                    responses.add(resp)
 | 
			
		||||
                else:
 | 
			
		||||
                    # no responses: remove response reference to avoid missing
 | 
			
		||||
                    # response warning of exporter
 | 
			
		||||
                    newSensor.setResponse("")
 | 
			
		||||
 | 
			
		||||
        objCount += newInv.sensorCount()
 | 
			
		||||
        resCount = len(responses)
 | 
			
		||||
        if not self.checkObjects(req, objCount + resCount, maxObj):
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        # responses
 | 
			
		||||
        if ro.includeRes:
 | 
			
		||||
            if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
                return None
 | 
			
		||||
            for i in range(inv.responsePAZCount()):
 | 
			
		||||
                resp = inv.responsePAZ(i)
 | 
			
		||||
                if resp.publicID() in responses:
 | 
			
		||||
                    newInv.add(seiscomp.datamodel.ResponsePAZ(resp))
 | 
			
		||||
            if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
                return None
 | 
			
		||||
            for i in range(inv.responseFIRCount()):
 | 
			
		||||
                resp = inv.responseFIR(i)
 | 
			
		||||
                if resp.publicID() in responses:
 | 
			
		||||
                    newInv.add(seiscomp.datamodel.ResponseFIR(resp))
 | 
			
		||||
            if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
                return None
 | 
			
		||||
            for i in range(inv.responsePolynomialCount()):
 | 
			
		||||
                resp = inv.responsePolynomial(i)
 | 
			
		||||
                if resp.publicID() in responses:
 | 
			
		||||
                    newInv.add(seiscomp.datamodel.ResponsePolynomial(resp))
 | 
			
		||||
            if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
                return None
 | 
			
		||||
            for i in range(inv.responseFAPCount()):
 | 
			
		||||
                resp = inv.responseFAP(i)
 | 
			
		||||
                if resp.publicID() in responses:
 | 
			
		||||
                    newInv.add(seiscomp.datamodel.ResponseFAP(resp))
 | 
			
		||||
            if req._disconnected:  # pylint: disable=W0212
 | 
			
		||||
                return None
 | 
			
		||||
            for i in range(inv.responseIIRCount()):
 | 
			
		||||
                resp = inv.responseIIR(i)
 | 
			
		||||
                if resp.publicID() in responses:
 | 
			
		||||
                    newInv.add(seiscomp.datamodel.ResponseIIR(resp))
 | 
			
		||||
 | 
			
		||||
        return decCount
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# vim: ts=4 et
 | 
			
		||||
							
								
								
									
										201
									
								
								lib/python/seiscomp/fdsnws/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								lib/python/seiscomp/fdsnws/utils.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,201 @@
 | 
			
		||||
################################################################################
 | 
			
		||||
# Copyright (C) 2013-2014 gempa GmbH
 | 
			
		||||
#
 | 
			
		||||
# Common utility functions
 | 
			
		||||
#
 | 
			
		||||
# Author:  Stephan Herrnkind
 | 
			
		||||
# Email:   herrnkind@gempa.de
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
import socket
 | 
			
		||||
import traceback
 | 
			
		||||
 | 
			
		||||
import twisted
 | 
			
		||||
 | 
			
		||||
from twisted.internet import reactor, defer
 | 
			
		||||
from twisted.python.failure import Failure
 | 
			
		||||
from twisted.web import http
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import seiscomp.logging
 | 
			
		||||
import seiscomp.core
 | 
			
		||||
import seiscomp.io
 | 
			
		||||
from seiscomp.client import Application
 | 
			
		||||
 | 
			
		||||
twisted_version = (twisted.version.major, twisted.version.minor, twisted.version.micro)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -------------------------------------------------------------------------------
 | 
			
		||||
# Converts a unicode string to a byte string
 | 
			
		||||
def b_str(unicode_string):
 | 
			
		||||
    return unicode_string.encode("utf-8", "replace")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -------------------------------------------------------------------------------
 | 
			
		||||
# Converts a byte string to a unicode string
 | 
			
		||||
def u_str(byte_string):
 | 
			
		||||
    return byte_string.decode("utf-8", "replace")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -------------------------------------------------------------------------------
 | 
			
		||||
# Tests if a SC3 inventory object is restricted
 | 
			
		||||
def isRestricted(obj):
 | 
			
		||||
    try:
 | 
			
		||||
        return obj.restricted()
 | 
			
		||||
    except ValueError:
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -------------------------------------------------------------------------------
 | 
			
		||||
# Thread-safe write of string data using reactor main thread
 | 
			
		||||
def writeTS(req, data):
 | 
			
		||||
    reactor.callFromThread(req.write, b_str(data))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -------------------------------------------------------------------------------
 | 
			
		||||
# Thread-safe write of binary data using reactor main thread
 | 
			
		||||
def writeTSBin(req, data):
 | 
			
		||||
    reactor.callFromThread(req.write, data)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -------------------------------------------------------------------------------
 | 
			
		||||
# Finish requests deferred to threads
 | 
			
		||||
def onFinish(result, req):
 | 
			
		||||
    seiscomp.logging.debug(f"finish value = {str(result)}")
 | 
			
		||||
    if isinstance(result, Failure):
 | 
			
		||||
        err = result.value
 | 
			
		||||
        if isinstance(err, defer.CancelledError):
 | 
			
		||||
            seiscomp.logging.error("request canceled")
 | 
			
		||||
            return
 | 
			
		||||
        seiscomp.logging.error(
 | 
			
		||||
            f"{result.getErrorMessage()} "
 | 
			
		||||
            f"{traceback.format_tb(result.getTracebackObject())}"
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        if result:
 | 
			
		||||
            seiscomp.logging.debug("request successfully served")
 | 
			
		||||
        else:
 | 
			
		||||
            seiscomp.logging.debug("request failed")
 | 
			
		||||
 | 
			
		||||
    reactor.callFromThread(req.finish)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -------------------------------------------------------------------------------
 | 
			
		||||
# Handle connection errors
 | 
			
		||||
def onCancel(failure, req):
 | 
			
		||||
    if failure:
 | 
			
		||||
        seiscomp.logging.error(
 | 
			
		||||
            f"{failure.getErrorMessage()} "
 | 
			
		||||
            f"{traceback.format_tb(failure.getTracebackObject())}"
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        seiscomp.logging.error("request canceled")
 | 
			
		||||
    req.cancel()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -------------------------------------------------------------------------------
 | 
			
		||||
# Handle premature connection reset
 | 
			
		||||
def onResponseFailure(_, call):
 | 
			
		||||
    seiscomp.logging.error("response canceled")
 | 
			
		||||
    call.cancel()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -------------------------------------------------------------------------------
 | 
			
		||||
# Renders error page if the result set exceeds the configured maximum number
 | 
			
		||||
# objects
 | 
			
		||||
def accessLog(req, ro, code, length, err):
 | 
			
		||||
    logger = Application.Instance()._accessLog  # pylint: disable=W0212
 | 
			
		||||
    if logger is None:
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    logger.log(AccessLogEntry(req, ro, code, length, err))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -------------------------------------------------------------------------------
 | 
			
		||||
# Compability function for stringToDatetime() change in Twisted 24.7, see
 | 
			
		||||
# https://github.com/twisted/twisted/commit/731e370dfc5d2f7224dc1e12931ddf5c51b211a6
 | 
			
		||||
def stringToDatetime(dateString):
 | 
			
		||||
    if twisted_version < (24, 7):
 | 
			
		||||
        return http.stringToDatetime(dateString)
 | 
			
		||||
 | 
			
		||||
    # Since version 24.7 the argument needs to be a byte string
 | 
			
		||||
    return http.stringToDatetime(dateString.encode("ascii"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
class Sink(seiscomp.io.ExportSink):
 | 
			
		||||
    def __init__(self, request):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
 | 
			
		||||
        self.request = request
 | 
			
		||||
        self.written = 0
 | 
			
		||||
 | 
			
		||||
    def write(self, data):
 | 
			
		||||
        if self.request._disconnected:  # pylint: disable=W0212
 | 
			
		||||
            return -1
 | 
			
		||||
 | 
			
		||||
        writeTSBin(self.request, data)
 | 
			
		||||
        self.written += len(data)
 | 
			
		||||
        return len(data)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
class AccessLogEntry:
 | 
			
		||||
    def __init__(self, req, ro, code, length, err):
 | 
			
		||||
        # user agent
 | 
			
		||||
        agent = req.getHeader("User-Agent")
 | 
			
		||||
        if agent is None:
 | 
			
		||||
            agent = ""
 | 
			
		||||
        else:
 | 
			
		||||
            agent = agent[:100].replace("|", " ")
 | 
			
		||||
 | 
			
		||||
        if err is None:
 | 
			
		||||
            err = ""
 | 
			
		||||
 | 
			
		||||
        service, user, accessTime, procTime = "", "", "", 0
 | 
			
		||||
        net, sta, loc, cha = "", "", "", ""
 | 
			
		||||
        if ro is not None:
 | 
			
		||||
            # processing time in milliseconds
 | 
			
		||||
            procTime = int((seiscomp.core.Time.GMT() - ro.accessTime).length() * 1000.0)
 | 
			
		||||
 | 
			
		||||
            service = ro.service
 | 
			
		||||
            if ro.userName is not None:
 | 
			
		||||
                user = ro.userName
 | 
			
		||||
            accessTime = str(ro.accessTime)
 | 
			
		||||
 | 
			
		||||
            if ro.channel is not None:
 | 
			
		||||
                if ro.channel.net is not None:
 | 
			
		||||
                    net = ",".join(ro.channel.net)
 | 
			
		||||
                if ro.channel.sta is not None:
 | 
			
		||||
                    sta = ",".join(ro.channel.sta)
 | 
			
		||||
                if ro.channel.loc is not None:
 | 
			
		||||
                    loc = ",".join(ro.channel.loc)
 | 
			
		||||
                if ro.channel.cha is not None:
 | 
			
		||||
                    cha = ",".join(ro.channel.cha)
 | 
			
		||||
 | 
			
		||||
        # The host name of the client is resolved in the __str__ method by the
 | 
			
		||||
        # logging thread so that a long running DNS reverse lookup may not slow
 | 
			
		||||
        # down the request
 | 
			
		||||
        self.msgPrefix = f"{service}|{u_str(req.getRequestHostname())}|{accessTime}|"
 | 
			
		||||
 | 
			
		||||
        xff = req.requestHeaders.getRawHeaders("x-forwarded-for")
 | 
			
		||||
        if xff:
 | 
			
		||||
            self.userIP = xff[0].split(",")[0].strip()
 | 
			
		||||
        else:
 | 
			
		||||
            self.userIP = req.getClientIP()
 | 
			
		||||
 | 
			
		||||
        self.clientIP = req.getClientIP()
 | 
			
		||||
        self.msgSuffix = (
 | 
			
		||||
            f"|{self.clientIP}|{length}|{procTime}|{err}|{agent}|{code}|{user}|{net}"
 | 
			
		||||
            f"|{sta}|{loc}|{cha}||"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        try:
 | 
			
		||||
            userHost = socket.gethostbyaddr(self.userIP)[0]
 | 
			
		||||
        except socket.herror:
 | 
			
		||||
            userHost = self.userIP
 | 
			
		||||
        return self.msgPrefix + userHost + self.msgSuffix
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# vim: ts=4 et
 | 
			
		||||
							
								
								
									
										1275
									
								
								lib/python/seiscomp/geo.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1275
									
								
								lib/python/seiscomp/geo.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										2532
									
								
								lib/python/seiscomp/io.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2532
									
								
								lib/python/seiscomp/io.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										386
									
								
								lib/python/seiscomp/kernel.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										386
									
								
								lib/python/seiscomp/kernel.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,386 @@
 | 
			
		||||
############################################################################
 | 
			
		||||
#    Copyright (C) by gempa GmbH, GFZ Potsdam                              #
 | 
			
		||||
#                                                                          #
 | 
			
		||||
#    You can redistribute and/or modify this program under the             #
 | 
			
		||||
#    terms of the SeisComP Public License.                                 #
 | 
			
		||||
#                                                                          #
 | 
			
		||||
#    This program is distributed in the hope that it will be useful,       #
 | 
			
		||||
#    but WITHOUT ANY WARRANTY; without even the implied warranty of        #
 | 
			
		||||
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         #
 | 
			
		||||
#    SeisComP Public License for more details.                             #
 | 
			
		||||
############################################################################
 | 
			
		||||
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
import string
 | 
			
		||||
import subprocess
 | 
			
		||||
import seiscomp.config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Template(string.Template):
 | 
			
		||||
    idpattern = r'[_a-z][_a-z0-9.]*'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Environment(seiscomp.config.Config):
 | 
			
		||||
    def __init__(self, rootPath):
 | 
			
		||||
        seiscomp.config.Config.__init__(self)
 | 
			
		||||
        self.SEISCOMP_ROOT = rootPath
 | 
			
		||||
        try:
 | 
			
		||||
            self.home_dir = os.environ["HOME"]
 | 
			
		||||
        except:
 | 
			
		||||
            self.home_dir = "."
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.local_config_dir = os.environ["SEISCOMP_LOCAL_CONFIG"]
 | 
			
		||||
        except:
 | 
			
		||||
            self.local_config_dir = os.path.join(self.home_dir, ".seiscomp")
 | 
			
		||||
 | 
			
		||||
        self.root = rootPath
 | 
			
		||||
        self.bin_dir = os.path.join(self.root, "bin")
 | 
			
		||||
        self.data_dir = os.path.join(self.root, "share")
 | 
			
		||||
        self.etc_dir = os.path.join(self.root, "etc")
 | 
			
		||||
        self.etc_defaults_dir = os.path.join(self.root, "etc", "defaults")
 | 
			
		||||
        self.descriptions_dir = os.path.join(self.root, "etc", "descriptions")
 | 
			
		||||
        self.key_dir = os.path.join(self.root, "etc", "key")
 | 
			
		||||
        self.var_dir = os.path.join(self.root, "var")
 | 
			
		||||
        self.log_dir = os.path.join(self.local_config_dir, "log")
 | 
			
		||||
        self.cwd = None
 | 
			
		||||
        self.last_template_file = None
 | 
			
		||||
 | 
			
		||||
        self._csv = False
 | 
			
		||||
        self._readConfig()
 | 
			
		||||
 | 
			
		||||
        os.environ["SEISCOMP_ROOT"] = self.SEISCOMP_ROOT
 | 
			
		||||
 | 
			
		||||
        # Add LD_LIBRARY_PATH and PATH to OS environment
 | 
			
		||||
        LD_LIBRARY_PATH = os.path.join(self.SEISCOMP_ROOT, "lib")
 | 
			
		||||
        BIN_PATH = os.path.join(self.SEISCOMP_ROOT, "bin")
 | 
			
		||||
        SBIN_PATH = os.path.join(self.SEISCOMP_ROOT, "sbin")
 | 
			
		||||
        PATH = BIN_PATH + ":" + SBIN_PATH
 | 
			
		||||
        PYTHONPATH = os.path.join(self.SEISCOMP_ROOT, "lib", "python")
 | 
			
		||||
        try:
 | 
			
		||||
            LD_LIBRARY_PATH = os.environ["LD_LIBRARY_PATH"] + \
 | 
			
		||||
                ":" + LD_LIBRARY_PATH
 | 
			
		||||
        except:
 | 
			
		||||
            pass
 | 
			
		||||
        os.environ["LD_LIBRARY_PATH"] = LD_LIBRARY_PATH
 | 
			
		||||
        try:
 | 
			
		||||
            PATH = PATH + ":" + os.environ["PATH"]
 | 
			
		||||
        except:
 | 
			
		||||
            pass
 | 
			
		||||
        os.environ["PATH"] = PATH
 | 
			
		||||
        try:
 | 
			
		||||
            PYTHONPATH = os.environ["PYTHONPATH"] + ":" + PYTHONPATH
 | 
			
		||||
        except:
 | 
			
		||||
            pass
 | 
			
		||||
        os.environ["PYTHONPATH"] = PYTHONPATH
 | 
			
		||||
 | 
			
		||||
        # Create required directories
 | 
			
		||||
        try:
 | 
			
		||||
            os.makedirs(os.path.join(self.root, "var", "log"))
 | 
			
		||||
        except:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            os.makedirs(os.path.join(self.root, "var", "run"))
 | 
			
		||||
        except:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    def _readConfig(self):
 | 
			
		||||
        self.syslog = False
 | 
			
		||||
 | 
			
		||||
        # Read configuration file
 | 
			
		||||
        kernelCfg = os.path.join(self.root, "etc", "kernel.cfg")
 | 
			
		||||
        if self.readConfig(kernelCfg) == False:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.syslog = self.getBool("syslog")
 | 
			
		||||
        except:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    # Changes into the SEISCOMP_ROOT directory
 | 
			
		||||
    def chroot(self):
 | 
			
		||||
        if self.root:
 | 
			
		||||
            # Remember current directory
 | 
			
		||||
            self.cwd = os.getcwd()
 | 
			
		||||
            os.chdir(self.SEISCOMP_ROOT)
 | 
			
		||||
            self.root = ""
 | 
			
		||||
 | 
			
		||||
    # Changes back to the current workdir
 | 
			
		||||
    def chback(self):
 | 
			
		||||
        if self.cwd:
 | 
			
		||||
            os.chdir(self.cwd)
 | 
			
		||||
            self.cwd = None
 | 
			
		||||
            self.root = self.SEISCOMP_ROOT
 | 
			
		||||
 | 
			
		||||
    def resolvePath(self, path):
 | 
			
		||||
        return path.replace("@LOGDIR@", self.log_dir)\
 | 
			
		||||
                   .replace("@CONFIGDIR@", self.local_config_dir)\
 | 
			
		||||
                   .replace("@DEFAULTCONFIGDIR@", self.etc_defaults_dir)\
 | 
			
		||||
                   .replace("@SYSTEMCONFIGDIR@", self.etc_dir)\
 | 
			
		||||
                   .replace("@ROOTDIR@", self.root)\
 | 
			
		||||
                   .replace("@DATADIR@", self.data_dir)\
 | 
			
		||||
                   .replace("@KEYDIR@", self.key_dir)\
 | 
			
		||||
                   .replace("@HOMEDIR@", self.home_dir)
 | 
			
		||||
 | 
			
		||||
    def setCSVOutput(self, csv):
 | 
			
		||||
        self._csv = csv
 | 
			
		||||
 | 
			
		||||
    def enableModule(self, name):
 | 
			
		||||
        runFile = os.path.join(self.root, "etc", "init", name + ".auto")
 | 
			
		||||
        if os.path.exists(runFile):
 | 
			
		||||
            print("%s is already enabled" % name)
 | 
			
		||||
            return 0
 | 
			
		||||
        try:
 | 
			
		||||
            open(runFile, 'w').close()
 | 
			
		||||
            print("enabled %s" % name)
 | 
			
		||||
            return 0
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            sys.stderr.write(str(exc) + "\n")
 | 
			
		||||
            sys.stderr.flush()
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    def disableModule(self, name):
 | 
			
		||||
        runFile = os.path.join(self.root, "etc", "init", name + ".auto")
 | 
			
		||||
        if not os.path.exists(runFile):
 | 
			
		||||
            print("%s is not enabled" % name)
 | 
			
		||||
            return 0
 | 
			
		||||
        try:
 | 
			
		||||
            os.remove(runFile)
 | 
			
		||||
            print("disabled %s" % name)
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            sys.stderr.write(str(exc) + "\n")
 | 
			
		||||
            sys.stderr.flush()
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    def isModuleEnabled(self, name):
 | 
			
		||||
        runFile = os.path.join(self.root, "etc", "init", name + ".auto")
 | 
			
		||||
        return os.path.exists(runFile) == True
 | 
			
		||||
 | 
			
		||||
    # Return the module name from a path
 | 
			
		||||
    def moduleName(self, path):
 | 
			
		||||
        return os.path.splitext(os.path.basename(path))[0]
 | 
			
		||||
 | 
			
		||||
    # Returns a module's lockfile
 | 
			
		||||
    def lockFile(self, module):
 | 
			
		||||
        return os.path.join(self.root, "var", "run", module + ".pid")
 | 
			
		||||
 | 
			
		||||
    # Returns a module's runfile
 | 
			
		||||
    def runFile(self, module):
 | 
			
		||||
        return os.path.join(self.root, "var", "run", module + ".run")
 | 
			
		||||
 | 
			
		||||
    # Returns a module's logfile
 | 
			
		||||
    def logFile(self, module):
 | 
			
		||||
        return os.path.join(self.root, "var", "log", module + ".log")
 | 
			
		||||
 | 
			
		||||
    # Returns the binary file path of a given module name
 | 
			
		||||
    def binaryFile(self, module):
 | 
			
		||||
        # return os.path.join(self.root, "bin/" + module)
 | 
			
		||||
        return module
 | 
			
		||||
 | 
			
		||||
    def start(self, module, binary, params, nohup=False):
 | 
			
		||||
        cmd = binary + " " + params + " >" + self.logFile(module) + " 2>&1"
 | 
			
		||||
        if nohup:
 | 
			
		||||
            cmd = "nohup " + cmd + " &"
 | 
			
		||||
        return os.system(cmd)
 | 
			
		||||
 | 
			
		||||
    def stop(self, module, timeout):
 | 
			
		||||
        return self.killWait(module, timeout)
 | 
			
		||||
 | 
			
		||||
    def tryLock(self, module, timeout = None):
 | 
			
		||||
        if timeout is None:
 | 
			
		||||
            return subprocess.call("trylock " + self.lockFile(module), shell=True) == 0
 | 
			
		||||
        else:
 | 
			
		||||
            try:
 | 
			
		||||
                timeoutSeconds = int(timeout)
 | 
			
		||||
            except:
 | 
			
		||||
                print("Invalid timeout parameter, expected positive integer")
 | 
			
		||||
                raise
 | 
			
		||||
            return subprocess.call("waitlock %d \"%s\"" % (timeoutSeconds, self.lockFile(module)), shell=True) == 0
 | 
			
		||||
 | 
			
		||||
    def killWait(self, module, timeout):
 | 
			
		||||
        lockfile = self.lockFile(module)
 | 
			
		||||
 | 
			
		||||
        # Open pid file
 | 
			
		||||
        f = open(lockfile, "r")
 | 
			
		||||
 | 
			
		||||
        # Try to read the pid
 | 
			
		||||
        try:
 | 
			
		||||
            pid = int(f.readline())
 | 
			
		||||
        except:
 | 
			
		||||
            f.close()
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
        # Kill process with pid
 | 
			
		||||
        subprocess.call("kill %d" % pid, shell=True)
 | 
			
		||||
        if subprocess.call("waitlock %d \"%s\"" % (timeout, lockfile), shell=True) != 0:
 | 
			
		||||
            print("timeout exceeded")
 | 
			
		||||
            subprocess.call("kill -9 %d" % pid, shell=True)
 | 
			
		||||
 | 
			
		||||
        # Remove pid file
 | 
			
		||||
        try:
 | 
			
		||||
            os.remove(lockfile)
 | 
			
		||||
        except:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def processTemplate(self, templateFile, paths, params, printError=False):
 | 
			
		||||
        self.last_template_file = None
 | 
			
		||||
 | 
			
		||||
        for tp in paths:
 | 
			
		||||
            if os.path.exists(os.path.join(tp, templateFile)):
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            if printError:
 | 
			
		||||
                print("Error: template %s not found" % templateFile)
 | 
			
		||||
            return ""
 | 
			
		||||
 | 
			
		||||
        filename = os.path.join(tp, templateFile)
 | 
			
		||||
        self.last_template_file = filename
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            t = Template(open(filename).read())
 | 
			
		||||
        except:
 | 
			
		||||
            if printError:
 | 
			
		||||
                print("Error: template %s not readable" % filename)
 | 
			
		||||
            return ""
 | 
			
		||||
 | 
			
		||||
        params['date'] = time.ctime()
 | 
			
		||||
        params['template'] = filename
 | 
			
		||||
 | 
			
		||||
        while True:
 | 
			
		||||
            try:
 | 
			
		||||
                return t.substitute(params)
 | 
			
		||||
 | 
			
		||||
            except KeyError as e:
 | 
			
		||||
                print("warning: $%s is not defined in %s" % (e.args[0], filename))
 | 
			
		||||
                params[e.args[0]] = ""
 | 
			
		||||
 | 
			
		||||
            except ValueError as e:
 | 
			
		||||
                raise ValueError("%s: %s" % (filename, str(e)))
 | 
			
		||||
 | 
			
		||||
    def logStatus(self, name, isRunning, shouldRun, isEnabled):
 | 
			
		||||
        if self._csv == False:
 | 
			
		||||
            sys.stdout.write("%-20s is " % name)
 | 
			
		||||
            if not isRunning:
 | 
			
		||||
                sys.stdout.write("not ")
 | 
			
		||||
            sys.stdout.write("running")
 | 
			
		||||
            if not isRunning and shouldRun:
 | 
			
		||||
                sys.stdout.write(" [WARNING]")
 | 
			
		||||
            sys.stdout.write("\n")
 | 
			
		||||
        else:
 | 
			
		||||
            sys.stdout.write("%s;%d;%d;%d\n" % (
 | 
			
		||||
                name, int(isRunning), int(shouldRun), int(isEnabled)))
 | 
			
		||||
        sys.stdout.flush()
 | 
			
		||||
 | 
			
		||||
    def log(self, line):
 | 
			
		||||
        sys.stdout.write(line + "\n")
 | 
			
		||||
        sys.stdout.flush()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# The module interface which implementes the basic default operations.
 | 
			
		||||
# Each script can define its own handlers to customize the behaviour.
 | 
			
		||||
# Available handlers:
 | 
			
		||||
#  start()
 | 
			
		||||
#  stop()
 | 
			
		||||
#  check()
 | 
			
		||||
#  status(shouldRun)
 | 
			
		||||
#  setup(params = dict{name, values as []})
 | 
			
		||||
#  updateConfig()
 | 
			
		||||
class Module:
 | 
			
		||||
    def __init__(self, env, name):
 | 
			
		||||
        self.env = env
 | 
			
		||||
        self.name = name
 | 
			
		||||
        # The start order
 | 
			
		||||
        self.order = 100
 | 
			
		||||
        # Defines if this is a kernel module or not.
 | 
			
		||||
        # Kernel modules are always started
 | 
			
		||||
        self.isKernelModule = False
 | 
			
		||||
        # Defines if this is a config only module
 | 
			
		||||
        self.isConfigModule = False
 | 
			
		||||
        # Set default timeout when stopping a module to 10 seconds before killing it
 | 
			
		||||
        self.killTimeout = 10
 | 
			
		||||
        # Set default timeout when reloading a module to 10 seconds
 | 
			
		||||
        self.reloadTimeout = 10
 | 
			
		||||
 | 
			
		||||
    def _get_start_params(self):
 | 
			
		||||
        # Run as daemon
 | 
			
		||||
        params = "-D"
 | 
			
		||||
 | 
			
		||||
        # Enable syslog if configured
 | 
			
		||||
        if self.env.syslog == True:
 | 
			
		||||
            params = params + "s"
 | 
			
		||||
 | 
			
		||||
        params = params + " -l " + self.env.lockFile(self.name)
 | 
			
		||||
        return params
 | 
			
		||||
 | 
			
		||||
    def _run(self):
 | 
			
		||||
        return self.env.start(self.name, self.env.binaryFile(self.name), self._get_start_params())
 | 
			
		||||
 | 
			
		||||
    def isRunning(self):
 | 
			
		||||
        return self.env.tryLock(self.name) == False
 | 
			
		||||
 | 
			
		||||
    def start(self):
 | 
			
		||||
        if self.isRunning():
 | 
			
		||||
            self.env.log("%s is already running" % self.name)
 | 
			
		||||
            return 1
 | 
			
		||||
 | 
			
		||||
        self.env.log("starting %s" % self.name)
 | 
			
		||||
        return self._run()
 | 
			
		||||
 | 
			
		||||
    def stop(self):
 | 
			
		||||
        if not self.isRunning():
 | 
			
		||||
            self.env.log("%s is not running" % self.name)
 | 
			
		||||
            return 1
 | 
			
		||||
 | 
			
		||||
        self.env.log("shutting down %s" % self.name)
 | 
			
		||||
        # Default timeout to 10 seconds
 | 
			
		||||
        return self.env.stop(self.name, self.killTimeout)
 | 
			
		||||
 | 
			
		||||
    def reload(self):
 | 
			
		||||
        self.env.log("reload not supported by %s" % self.name)
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
    # Check is the same as start. If a module should be checked
 | 
			
		||||
    # is decided by the control script which check the existence
 | 
			
		||||
    # of a corresponding run file.
 | 
			
		||||
    def check(self):
 | 
			
		||||
        return self.start()
 | 
			
		||||
 | 
			
		||||
    def status(self, shouldRun):
 | 
			
		||||
        self.env.logStatus(self.name, self.isRunning(), shouldRun, self.env.isModuleEnabled(
 | 
			
		||||
            self.name) or isinstance(self, CoreModule))
 | 
			
		||||
 | 
			
		||||
    def requiresKernelModules(self):
 | 
			
		||||
        # The default handler triggers a start of kernel modules before updating
 | 
			
		||||
        # its configuration
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def updateConfigProxy(self):
 | 
			
		||||
        # This function must return either a string containing the module name
 | 
			
		||||
        # of the proxy module that should be configured as well or None.
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    def updateConfig(self):
 | 
			
		||||
        # This function must return a number indicating the error code where
 | 
			
		||||
        # 0 means no error. The default handler doesn't do anything.
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    def printCrontab(self):
 | 
			
		||||
        # The default handler doesn't do anything
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    def supportsAliases(self):
 | 
			
		||||
        # The default handler does not support aliases
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Define a kernel core module which is started always
 | 
			
		||||
class CoreModule(Module):
 | 
			
		||||
    def __init__(self, env, name):
 | 
			
		||||
        Module.__init__(self, env, name)
 | 
			
		||||
							
								
								
									
										0
									
								
								lib/python/seiscomp/legacy/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								lib/python/seiscomp/legacy/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										6
									
								
								lib/python/seiscomp/legacy/db/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								lib/python/seiscomp/legacy/db/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
from __future__ import (absolute_import, division, print_function,
 | 
			
		||||
                        unicode_literals)
 | 
			
		||||
 | 
			
		||||
class DBError(Exception):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										0
									
								
								lib/python/seiscomp/legacy/db/seiscomp3/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								lib/python/seiscomp/legacy/db/seiscomp3/__init__.py
									
									
									
									
									
										Normal file
									
								
							Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user