[seiscomp, scanloc] Install, add .gitignore

This commit is contained in:
2025-10-09 15:07:02 +02:00
commit 20f5301bb1
2848 changed files with 1315858 additions and 0 deletions

1
lib/libbson-1.0.so Symbolic link
View File

@ -0,0 +1 @@
libbson-1.0.so.0

1
lib/libbson-1.0.so.0 Symbolic link
View File

@ -0,0 +1 @@
libbson-1.0.so.0.0.0

BIN
lib/libbson-1.0.so.0.0.0 Normal file

Binary file not shown.

1
lib/libmseed.so Symbolic link
View File

@ -0,0 +1 @@
libmseed.so.2.19

BIN
lib/libmseed.so.2.19 Normal file

Binary file not shown.

BIN
lib/libseiscomp_broker.so Normal file

Binary file not shown.

1
lib/libseiscomp_client.so Symbolic link
View File

@ -0,0 +1 @@
libseiscomp_client.so.16

View File

@ -0,0 +1 @@
libseiscomp_client.so.16.4.0

Binary file not shown.

BIN
lib/libseiscomp_config.so Normal file

Binary file not shown.

1
lib/libseiscomp_core.so Symbolic link
View File

@ -0,0 +1 @@
libseiscomp_core.so.16

1
lib/libseiscomp_core.so.16 Symbolic link
View File

@ -0,0 +1 @@
libseiscomp_core.so.16.4.0

Binary file not shown.

BIN
lib/libseiscomp_daplugin.so Normal file

Binary file not shown.

Binary file not shown.

BIN
lib/libseiscomp_evplugin.so Normal file

Binary file not shown.

BIN
lib/libseiscomp_mplugin.so Normal file

Binary file not shown.

BIN
lib/libseiscomp_qcplugin.so Normal file

Binary file not shown.

1
lib/libseiscomp_qt.so Symbolic link
View File

@ -0,0 +1 @@
libseiscomp_qt.so.16

1
lib/libseiscomp_qt.so.16 Symbolic link
View File

@ -0,0 +1 @@
libseiscomp_qt.so.16.4.0

Binary file not shown.

BIN
lib/libseiscomp_unittest.so Normal file

Binary file not shown.

View 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

View File

View 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

View 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)

View 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

File diff suppressed because it is too large Load Diff

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
View 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

View 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)

View 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
View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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)

View File

@ -0,0 +1,4 @@
import os
import sys
sys.setdlopenflags(os.RTLD_LAZY | os.RTLD_GLOBAL)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
lib/python/seiscomp/_geo.so Normal file

Binary file not shown.

BIN
lib/python/seiscomp/_io.so Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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()

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

View 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)

View 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")

View 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

View 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,
)

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

2532
lib/python/seiscomp/io.py Normal file

File diff suppressed because it is too large Load Diff

View 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)

View File

View File

@ -0,0 +1,6 @@
from __future__ import (absolute_import, division, print_function,
unicode_literals)
class DBError(Exception):
pass

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,221 @@
# 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 _logging
else:
import _logging
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__)
SEISCOMP_COMPONENT = _logging.SEISCOMP_COMPONENT
SEISCOMP_LOG_API_VERSION = _logging.SEISCOMP_LOG_API_VERSION
LL_UNDEFINED = _logging.LL_UNDEFINED
LL_CRITICAL = _logging.LL_CRITICAL
LL_ERROR = _logging.LL_ERROR
LL_WARNING = _logging.LL_WARNING
LL_NOTICE = _logging.LL_NOTICE
LL_INFO = _logging.LL_INFO
LL_DEBUG = _logging.LL_DEBUG
LL_QUANTITY = _logging.LL_QUANTITY
SEISCOMP_LOGGING_CURRENT_FUNCTION = _logging.SEISCOMP_LOGGING_CURRENT_FUNCTION
def debug(*args):
return _logging.debug(*args)
def info(*args):
return _logging.info(*args)
def warning(*args):
return _logging.warning(*args)
def error(*args):
return _logging.error(*args)
def notice(*args):
return _logging.notice(*args)
def log(*args):
return _logging.log(*args)
def getAll():
return _logging.getAll()
def getGlobalChannel(*args):
return _logging.getGlobalChannel(*args)
def getComponentChannel(*args):
return _logging.getComponentChannel(*args)
def getComponentAll(component):
return _logging.getComponentAll(component)
def getComponentDebugs(component):
return _logging.getComponentDebugs(component)
def getComponentInfos(component):
return _logging.getComponentInfos(component)
def getComponentWarnings(component):
return _logging.getComponentWarnings(component)
def getComponentErrors(component):
return _logging.getComponentErrors(component)
def getComponentNotices(component):
return _logging.getComponentNotices(component)
def consoleOutput():
return _logging.consoleOutput()
def enableConsoleLogging(arg1):
return _logging.enableConsoleLogging(arg1)
def disableConsoleLogging():
return _logging.disableConsoleLogging()
class Output(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__ = _logging.delete_Output
def subscribe(self, channel):
return _logging.Output_subscribe(self, channel)
def unsubscribe(self, channel):
return _logging.Output_unsubscribe(self, channel)
def logComponent(self, e):
return _logging.Output_logComponent(self, e)
def logContext(self, e):
return _logging.Output_logContext(self, e)
def setUTCEnabled(self, e):
return _logging.Output_setUTCEnabled(self, e)
# Register Output in _logging:
_logging.Output_swigregister(Output)
class FdOutput(Output):
thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
__repr__ = _swig_repr
def __init__(self, fdOut=2):
_logging.FdOutput_swiginit(self, _logging.new_FdOutput(fdOut))
__swig_destroy__ = _logging.delete_FdOutput
# Register FdOutput in _logging:
_logging.FdOutput_swigregister(FdOutput)
class FileOutput(Output):
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):
_logging.FileOutput_swiginit(self, _logging.new_FileOutput(*args))
__swig_destroy__ = _logging.delete_FileOutput
def open(self, filename):
return _logging.FileOutput_open(self, filename)
def isOpen(self):
return _logging.FileOutput_isOpen(self)
# Register FileOutput in _logging:
_logging.FileOutput_swigregister(FileOutput)
class FileRotatorOutput(FileOutput):
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):
_logging.FileRotatorOutput_swiginit(self, _logging.new_FileRotatorOutput(*args))
def open(self, filename):
return _logging.FileRotatorOutput_open(self, filename)
__swig_destroy__ = _logging.delete_FileRotatorOutput
# Register FileRotatorOutput in _logging:
_logging.FileRotatorOutput_swigregister(FileRotatorOutput)
class SyslogOutput(Output):
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):
_logging.SyslogOutput_swiginit(self, _logging.new_SyslogOutput(*args))
__swig_destroy__ = _logging.delete_SyslogOutput
def facility(self):
return _logging.SyslogOutput_facility(self)
def open(self, ident, facility=None):
return _logging.SyslogOutput_open(self, ident, facility)
def isOpen(self):
return _logging.SyslogOutput_isOpen(self)
def close(self):
return _logging.SyslogOutput_close(self)
# Register SyslogOutput in _logging:
_logging.SyslogOutput_swigregister(SyslogOutput)

View File

@ -0,0 +1,54 @@
#*****************************************************************************
# logs.py
#
# SeisComP log handlers
#
# (c) 2005 Andres Heinloo, GFZ Potsdam
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version. For more information, see http://www.gnu.org/
#*****************************************************************************
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import sys as _sys
import traceback as _traceback
class _Logf(object):
def write(self, s):
error(s.rstrip())
def print_exc():
_traceback.print_exc(file=_Logf())
# Default handlers, to be overridden by packages, eg.:
#
# def log_info(s):
# print time.ctime() + " - trigger: " + s
# sys.stdout.flush()
#
# seiscomp.logs.info = log_info
def debug(s):
_sys.stdout.write(s + "\n")
_sys.stdout.flush()
def info(s):
_sys.stdout.write(s + "\n")
_sys.stdout.flush()
def notice(s):
_sys.stdout.write(s + "\n")
_sys.stdout.flush()
def warning(s):
_sys.stdout.write(s + "\n")
_sys.stdout.flush()
def error(s):
_sys.stdout.write(s + "\n")
_sys.stdout.flush()

3139
lib/python/seiscomp/math.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,491 @@
"""Python-only Mini-SEED module with limited functionality.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
:Copyright:
2005 Andres Heinloo, GEOFON, GFZ Potsdam <geofon@gfz-potsdam.de>
:License:
GPLv3
:Platform:
Linux
.. moduleauthor:: Andres Heinloo <andres@gfz-potsdam.de>, GEOFON, GFZ Potsdam
"""
from __future__ import absolute_import, division, print_function
import datetime
import struct
import sys
from io import BytesIO
_FIXHEAD_LEN = 48
_BLKHEAD_LEN = 4
_BLK1000_LEN = 4
_BLK1001_LEN = 4
_MAX_RECLEN = 4096
_doy = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
def _is_leap(y):
"""True if y is a leap year."""
return (y % 400 == 0) or (y % 4 == 0 and y % 100 != 0)
def _ldoy(y, m):
"""The day of the year of the first day of month m, in year y.
Note: for January, m=1; for December, m=12.
Examples:
_ldoy(1900, 4) = 90
_ldoy(1900, 1) = 0
_ldoy(1999, 4) = 90
_ldoy(2004, 4) = 91
_ldoy(2000, 4) = 91
"""
return _doy[m - 1] + (_is_leap(y) and m >= 3)
def _dy2mdy(doy, year):
month = 1
while doy > _ldoy(year, month + 1):
month += 1
mday = doy - _ldoy(year, month)
return (month, mday)
def _mdy2dy(month, day, year):
return _ldoy(year, month) + day
class EndOfData(Exception):
"""."""
class MSeedError(Exception):
"""."""
class MSeedNoData(MSeedError):
"""."""
class Record(object):
"""Mini-SEED record."""
def __init__(self, src):
"""Create a Mini-SEED record from a file handle or a bitstream."""
if type(src) == bytes:
fd = BytesIO(src)
elif hasattr(src, "read"):
fd = src
else:
raise TypeError("argument is neither bytes nor a file object")
# self.header = ""
self.header = bytes()
fixhead = fd.read(_FIXHEAD_LEN)
if len(fixhead) == 0:
# FIXME Check if there is no better option, but NOT StopIteration!
raise EndOfData
if len(fixhead) < _FIXHEAD_LEN:
raise MSeedError("unexpected end of header")
(
recno_str,
self.rectype,
sta,
loc,
cha,
net,
bt_year,
bt_doy,
bt_hour,
bt_minute,
bt_second,
bt_tms,
self.nsamp,
self.sr_factor,
self.sr_mult,
self.aflgs,
self.cflgs,
self.qflgs,
self.__num_blk,
self.time_correction,
self.__pdata,
self.__pblk,
) = struct.unpack(">6scx5s2s3s2s2H3Bx2H2h4Bl2H", fixhead)
if sys.version_info[0] > 2:
recno_str = recno_str.decode("utf-8")
self.rectype = self.rectype.decode("utf-8")
sta = sta.decode("utf-8")
loc = loc.decode("utf-8")
cha = cha.decode("utf-8")
net = net.decode("utf-8")
self.header += fixhead
if self.rectype not in ("D", "R", "Q", "M"):
fd.read(_MAX_RECLEN - _FIXHEAD_LEN)
raise MSeedNoData("non-data record")
if self.__pdata >= _MAX_RECLEN:
raise MSeedError(
f"invalid pointer at {net.strip()}.{sta.strip()}.{loc.strip()}.{cha.strip()}: "
f"record size ({self.__pdata}) >= {_MAX_RECLEN}"
)
if self.__pdata < _FIXHEAD_LEN or (
self.__pblk != 0
and ((self.__pblk < _FIXHEAD_LEN) or (self.__pblk >= self.__pdata))
):
raise MSeedError(
f"invalid pointer at {net.strip()}.{sta.strip()}.{loc.strip()}.{cha.strip()}"
)
if self.__pblk == 0:
blklen = 0
else:
blklen = self.__pdata - self.__pblk
gaplen = self.__pblk - _FIXHEAD_LEN
gap = fd.read(gaplen)
if len(gap) < gaplen:
raise MSeedError("unexpected end of data")
self.header += gap
# defaults
self.encoding = 11
self.byteorder = 1
rec_len_exp = 12
self.time_quality = -1
micros = 0
self.nframes = None
self.__rec_len_exp_idx = None
self.__micros_idx = None
self.__nframes_idx = None
pos = 0
while pos < blklen:
blkhead = fd.read(_BLKHEAD_LEN)
if len(blkhead) < _BLKHEAD_LEN:
raise MSeedError(f"unexpected end of blockettes at{pos}{len(blkhead)}")
(blktype, nextblk) = struct.unpack(">2H", blkhead)
self.header += blkhead
pos += _BLKHEAD_LEN
if blktype == 1000:
blk1000 = fd.read(_BLK1000_LEN)
if len(blk1000) < _BLK1000_LEN:
raise MSeedError(
f"unexpected end of blockettes at {pos}{len(blk1000)}"
)
(self.encoding, self.byteorder, rec_len_exp) = struct.unpack(
">3Bx", blk1000
)
self.__rec_len_exp_idx = self.__pblk + pos + 2
self.header += blk1000
pos += _BLK1000_LEN
elif blktype == 1001:
blk1001 = fd.read(_BLK1001_LEN)
if len(blk1001) < _BLK1001_LEN:
raise MSeedError(
f"unexpected end of blockettes at {pos}{len(blk1001)}"
)
(self.time_quality, micros, self.nframes) = struct.unpack(
">BbxB", blk1001
)
self.__micros_idx = self.__pblk + pos + 1
self.__nframes_idx = self.__pblk + pos + 3
self.header += blk1001
pos += _BLK1001_LEN
if nextblk == 0:
break
if nextblk < self.__pblk + pos or nextblk >= self.__pdata:
raise MSeedError("invalid pointers")
gaplen = nextblk - (self.__pblk + pos)
gap = fd.read(gaplen)
if len(gap) < gaplen:
raise MSeedError("unexpected end of data")
self.header += gap
pos += gaplen
if pos > blklen:
raise MSeedError("corrupt record")
gaplen = self.__pdata - len(self.header)
gap = fd.read(gaplen)
if len(gap) < gaplen:
raise MSeedError("unexpected end of data")
self.header += gap
pos += gaplen
self.recno = int(recno_str)
self.net = net.strip()
self.sta = sta.strip()
self.loc = loc.strip()
self.cha = cha.strip()
if (self.sr_factor > 0) and (self.sr_mult > 0):
self.samprate_num = self.sr_factor * self.sr_mult
self.samprate_denom = 1
elif (self.sr_factor > 0) and (self.sr_mult < 0):
self.samprate_num = self.sr_factor
self.samprate_denom = -self.sr_mult
elif (self.sr_factor < 0) and (self.sr_mult > 0):
self.samprate_num = self.sr_mult
self.samprate_denom = -self.sr_factor
elif (self.sr_factor < 0) and (self.sr_mult < 0):
self.samprate_num = 1
self.samprate_denom = self.sr_factor * self.sr_mult
else:
self.samprate_num = 0
self.samprate_denom = 1
self.fsamp = float(self.samprate_num) / float(self.samprate_denom)
# quick fix to avoid exception from datetime
if bt_second > 59:
self.leap = bt_second - 59
bt_second = 59
else:
self.leap = 0
try:
(month, day) = _dy2mdy(bt_doy, bt_year)
self.begin_time = datetime.datetime(
bt_year, month, day, bt_hour, bt_minute, bt_second
)
self.begin_time += datetime.timedelta(microseconds=bt_tms * 100 + micros)
if (self.nsamp != 0) and (self.fsamp != 0):
msAux = 1000000 * self.nsamp / self.fsamp
self.end_time = self.begin_time + datetime.timedelta(microseconds=msAux)
else:
self.end_time = self.begin_time
except ValueError as e:
raise MSeedError(f"invalid time: {str(e)}")
self.size = 1 << rec_len_exp
if (self.size < len(self.header)) or (self.size > _MAX_RECLEN):
raise MSeedError("invalid record size")
datalen = self.size - self.__pdata
self.data = fd.read(datalen)
if len(self.data) < datalen:
raise MSeedError("unexpected end of data")
if len(self.header) + len(self.data) != self.size:
raise MSeedError("internal error")
(self.X0, self.Xn) = struct.unpack(">ll", self.data[4:12])
(w0,) = struct.unpack(">L", self.data[:4])
(w3,) = struct.unpack(">L", self.data[12:16])
c3 = (w0 >> 24) & 0x3
d0 = None
if self.encoding == 10:
# """STEIM (1) Compression?"""
if c3 == 1:
d0 = (w3 >> 24) & 0xFF
if d0 > 0x7F:
d0 -= 0x100
elif c3 == 2:
d0 = (w3 >> 16) & 0xFFFF
if d0 > 0x7FFF:
d0 -= 0x10000
elif c3 == 3:
d0 = w3 & 0xFFFFFFFF
if d0 > 0x7FFFFFFF:
d0 -= 0xFFFFFFFF
d0 -= 1
elif self.encoding == 11:
# """STEIM (2) Compression?"""
if c3 == 1:
d0 = (w3 >> 24) & 0xFF
if d0 > 0x7F:
d0 -= 0x100
elif c3 == 2:
dnib = (w3 >> 30) & 0x3
if dnib == 1:
d0 = w3 & 0x3FFFFFFF
if d0 > 0x1FFFFFFF:
d0 -= 0x40000000
elif dnib == 2:
d0 = (w3 >> 15) & 0x7FFF
if d0 > 0x3FFF:
d0 -= 0x8000
elif dnib == 3:
d0 = (w3 >> 20) & 0x3FF
if d0 > 0x1FF:
d0 -= 0x400
elif c3 == 3:
dnib = (w3 >> 30) & 0x3
if dnib == 0:
d0 = (w3 >> 24) & 0x3F
if d0 > 0x1F:
d0 -= 0x40
elif dnib == 1:
d0 = (w3 >> 25) & 0x1F
if d0 > 0xF:
d0 -= 0x20
elif dnib == 2:
d0 = (w3 >> 24) & 0xF
if d0 > 0x7:
d0 -= 0x10
if d0 is not None:
self.X_minus1 = self.X0 - d0
else:
self.X_minus1 = None
if (self.nframes is None) or (self.nframes == 0):
i = 0
self.nframes = 0
while i < len(self.data):
if self.data[i] == "\0":
break
i += 64
self.nframes += 1
def merge(self, rec):
"""Caller is expected to check for contiguity of data.
Check if rec.nframes * 64 <= len(data)?
"""
(self.Xn,) = struct.unpack(">l", rec.data[8:12])
self.data += rec.data[: rec.nframes * 64]
self.nframes += rec.nframes
self.nsamp += rec.nsamp
self.size = len(self.header) + len(self.data)
self.end_time = rec.end_time
def write(self, fd, rec_len_exp):
"""Write the record to an already opened file."""
if self.size > (1 << rec_len_exp):
raise MSeedError(
f"record is larger than requested write size: {self.size} > {1 << rec_len_exp}"
)
recno_str = bytes(("%06d" % (self.recno,)).encode("utf-8"))
sta = bytes(("%-5.5s" % (self.sta,)).encode("utf-8"))
loc = bytes(("%-2.2s" % (self.loc,)).encode("utf-8"))
cha = bytes(("%-3.3s" % (self.cha,)).encode("utf-8"))
net = bytes(("%-2.2s" % (self.net,)).encode("utf-8"))
bt_year = self.begin_time.year
bt_doy = _mdy2dy(
self.begin_time.month, self.begin_time.day, self.begin_time.year
)
bt_hour = self.begin_time.hour
bt_minute = self.begin_time.minute
bt_second = self.begin_time.second + self.leap
bt_tms = self.begin_time.microsecond // 100
micros = self.begin_time.microsecond % 100
# This is just to make it Python 2 AND 3 compatible (str vs. bytes)
rectype = (
self.rectype.encode("utf-8") if sys.version_info[0] > 2 else self.rectype
)
buf = struct.pack(
">6s2c5s2s3s2s2H3Bx2H2h4Bl2H",
recno_str,
rectype,
b" ",
sta,
loc,
cha,
net,
bt_year,
bt_doy,
bt_hour,
bt_minute,
bt_second,
bt_tms,
self.nsamp,
self.sr_factor,
self.sr_mult,
self.aflgs,
self.cflgs,
self.qflgs,
self.__num_blk,
self.time_correction,
self.__pdata,
self.__pblk,
)
fd.write(buf)
buf = list(self.header[_FIXHEAD_LEN:])
if self.__rec_len_exp_idx is not None:
buf[self.__rec_len_exp_idx - _FIXHEAD_LEN] = struct.pack(">B", rec_len_exp)
if self.__micros_idx is not None:
buf[self.__micros_idx - _FIXHEAD_LEN] = struct.pack(">b", micros)
if self.__nframes_idx is not None:
buf[self.__nframes_idx - _FIXHEAD_LEN] = struct.pack(">B", self.nframes)
ba = bytearray()
for b in buf:
try:
ba.append(b)
except Exception:
ba.append(int.from_bytes(b, byteorder="big"))
fd.write(ba)
buf = (
self.data[:4]
+ struct.pack(">ll", self.X0, self.Xn)
+ self.data[12:]
+ ((1 << rec_len_exp) - self.size) * b"\0"
)
fd.write(buf)
class Input(object):
"""Iterate over the available Mini-SEED records."""
def __init__(self, fd):
"""Create the iterable from the file handle passed as parameter."""
self.__fd = fd
def __iter__(self):
"""Define the iterator."""
while True:
try:
yield Record(self.__fd)
except EndOfData:
# This change follows new PEP-479, where it is explicitly forbidden to
# use StopIteration
# raise StopIteration
return
except MSeedNoData:
pass

View File

@ -0,0 +1,187 @@
from __future__ import print_function
import sys
import xml.dom.minidom
if sys.version_info[0] >= 3:
from configparser import RawConfigParser
else:
from ConfigParser import RawConfigParser
def readConfig(fileName):
cp = RawConfigParser()
fp = open(fileName, 'r')
if sys.version_info < (3, 2):
cp.readfp(fp) # pylint: disable=W1505
else:
cp.read_file(fp, fileName)
return cp
def parseXMLnode(root):
"""
Parses an XML tree starting from root node and returns a list of
tuples containing name, attributes and content of all child nodes.
"""
nodes = []
if root.hasChildNodes():
for node in [ node for node in root.childNodes
if node.nodeType==node.ELEMENT_NODE ]:
ncn = len(node.childNodes)
if ncn==0:
content = None
elif ncn==1 and node.firstChild.nodeValue:
content = node.firstChild.nodeValue.strip()
else:
content = parseXMLnode(node)
attrs = {}
if node.hasAttributes():
for i in range(node.attributes.length):
attr = node.attributes.item(i)
name = attr.nodeName
attrs[name] = attr.nodeValue.strip()
nodes.append((node.nodeName, attrs, content))
return nodes
def parseXMLfile(f):
root = xml.dom.minidom.parse(f)
x = parseXMLnode(root)
if len(x)==1:
return x[0]
# else not 1 root element, but that's cought by xml.dom.minidom.parse()
class MyConfig(dict):
def __init__(self, filename):
if filename[-4:].lower() == ".ini":
self.readINI(filename)
elif filename[-4:].lower() == ".xml":
self.readXML(filename)
else: print("XXXXXXXXXXXXXXX")
def readINI(self, filename):
config = readConfig(filename)
for sec in config.sections():
d = self[sec] = {}
for opt in config.options(sec):
d[opt] = config.get(sec, opt)
def readXML(self, filename):
# XXX XML support is only provided for testing.
name, attrs, content = parseXMLfile(filename)
assert name == "config"
for name, attrs, content in content:
assert "name" in attrs
sec = attrs["name"]
assert name == "section"
d = self[sec] = {}
for name, attrs, content in content:
if isinstance(content, list):
raise TypeError("<%s> elements can't have children" % name)
if name == "string":
tmp = str(content)
elif name == "int":
tmp = int(content)
elif name == "float":
tmp = float(content)
else:
raise NameError("illegal tag '%s'" % name)
if not "name" in attrs:
raise NameError("missing 'name' attribute in <%s>" % name)
opt = attrs["name"]
d[opt] = tmp
class ConfigINI(dict):
def __init__(self, filename, mandatory=None):
self.read(filename)
if not isinstance(mandatory,list):
mandatory = []
self.mandatory = mandatory
def read(self, filename):
config = readConfig(filename)
for sec in config.sections():
d = self[sec] = {}
for opt in config.options(sec):
d[opt] = config.get(sec, opt)
def fillDefault(self, defaultSection="default"):
default = self[defaultSection]
for section in self:
if section == defaultSection:
continue
# for missing items, use the default
for item in default:
if item not in self[section]:
self[section][item] = default[item]
# # check for items that don't appear in the default
# for item in self[section]:
# if item not in default and item not in self.mandatory:
# msg("[%s]: unmatched item '%s'" % (section, item))
for item in self.mandatory:
if item not in self[section]:
msg("[%s]: missing item '%s'" % (section, item))
# XXX this could also be treated as a fatal error
class ConfigXML(MyConfig):
def __init__(self, filename):
self.read(filename)
def read(self, filename):
# XXX XML support is only provided for testing.
name, attrs, content = parseXMLfile(filename)
assert name == "config"
for name, attrs, content in content:
assert "name" in attrs
sec = attrs["name"]
assert name == "section"
d = self[sec] = {}
for name, attrs, content in content:
if isinstance(content, list):
raise TypeError("<%s> elements can't have children" % name)
if name == "string":
tmp = str(content)
elif name == "int":
tmp = int(content)
elif name == "float":
tmp = float(content)
else:
raise NameError("illegal tag '%s'" % name)
if not "name" in attrs:
raise NameError("missing 'name' attribute in <%s>" % name)
opt = attrs["name"]
d[opt] = tmp
if __name__ == '__main__':
for f in "test.ini", "test.xml":
print("#### filename=", f)
config = MyConfig(f)
print(config)
for section in config:
print(section, config[section])

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,899 @@
# 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.
"""Codes for various seismological computations"""
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 _seismology
else:
import _seismology
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__ = _seismology.delete_SwigPyIterator
def value(self):
return _seismology.SwigPyIterator_value(self)
def incr(self, n=1):
return _seismology.SwigPyIterator_incr(self, n)
def decr(self, n=1):
return _seismology.SwigPyIterator_decr(self, n)
def distance(self, x):
return _seismology.SwigPyIterator_distance(self, x)
def equal(self, x):
return _seismology.SwigPyIterator_equal(self, x)
def copy(self):
return _seismology.SwigPyIterator_copy(self)
def next(self):
return _seismology.SwigPyIterator_next(self)
def __next__(self):
return _seismology.SwigPyIterator___next__(self)
def previous(self):
return _seismology.SwigPyIterator_previous(self)
def advance(self, n):
return _seismology.SwigPyIterator_advance(self, n)
def __eq__(self, x):
return _seismology.SwigPyIterator___eq__(self, x)
def __ne__(self, x):
return _seismology.SwigPyIterator___ne__(self, x)
def __iadd__(self, n):
return _seismology.SwigPyIterator___iadd__(self, n)
def __isub__(self, n):
return _seismology.SwigPyIterator___isub__(self, n)
def __add__(self, n):
return _seismology.SwigPyIterator___add__(self, n)
def __sub__(self, *args):
return _seismology.SwigPyIterator___sub__(self, *args)
def __iter__(self):
return self
# Register SwigPyIterator in _seismology:
_seismology.SwigPyIterator_swigregister(SwigPyIterator)
import seiscomp.io
import seiscomp.math
import seiscomp.core
import seiscomp.datamodel
import seiscomp.geo
import seiscomp.config
class TravelTimeList_internal(object):
r"""Proxy of C++ std::list< Seiscomp::TravelTime > class."""
thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
__repr__ = _swig_repr
def iterator(self):
r"""iterator(TravelTimeList_internal self) -> SwigPyIterator"""
return _seismology.TravelTimeList_internal_iterator(self)
def __iter__(self):
return self.iterator()
def __nonzero__(self):
r"""__nonzero__(TravelTimeList_internal self) -> bool"""
return _seismology.TravelTimeList_internal___nonzero__(self)
def __bool__(self):
r"""__bool__(TravelTimeList_internal self) -> bool"""
return _seismology.TravelTimeList_internal___bool__(self)
def __len__(self):
r"""__len__(TravelTimeList_internal self) -> std::list< Seiscomp::TravelTime >::size_type"""
return _seismology.TravelTimeList_internal___len__(self)
def __getslice__(self, i, j):
r"""__getslice__(TravelTimeList_internal self, std::list< Seiscomp::TravelTime >::difference_type i, std::list< Seiscomp::TravelTime >::difference_type j) -> TravelTimeList_internal"""
return _seismology.TravelTimeList_internal___getslice__(self, i, j)
def __setslice__(self, *args):
r"""
__setslice__(TravelTimeList_internal self, std::list< Seiscomp::TravelTime >::difference_type i, std::list< Seiscomp::TravelTime >::difference_type j)
__setslice__(TravelTimeList_internal self, std::list< Seiscomp::TravelTime >::difference_type i, std::list< Seiscomp::TravelTime >::difference_type j, TravelTimeList_internal v)
"""
return _seismology.TravelTimeList_internal___setslice__(self, *args)
def __delslice__(self, i, j):
r"""__delslice__(TravelTimeList_internal self, std::list< Seiscomp::TravelTime >::difference_type i, std::list< Seiscomp::TravelTime >::difference_type j)"""
return _seismology.TravelTimeList_internal___delslice__(self, i, j)
def __delitem__(self, *args):
r"""
__delitem__(TravelTimeList_internal self, std::list< Seiscomp::TravelTime >::difference_type i)
__delitem__(TravelTimeList_internal self, PySliceObject * slice)
"""
return _seismology.TravelTimeList_internal___delitem__(self, *args)
def __getitem__(self, *args):
r"""
__getitem__(TravelTimeList_internal self, PySliceObject * slice) -> TravelTimeList_internal
__getitem__(TravelTimeList_internal self, std::list< Seiscomp::TravelTime >::difference_type i) -> TravelTime
"""
return _seismology.TravelTimeList_internal___getitem__(self, *args)
def __setitem__(self, *args):
r"""
__setitem__(TravelTimeList_internal self, PySliceObject * slice, TravelTimeList_internal v)
__setitem__(TravelTimeList_internal self, PySliceObject * slice)
__setitem__(TravelTimeList_internal self, std::list< Seiscomp::TravelTime >::difference_type i, TravelTime x)
"""
return _seismology.TravelTimeList_internal___setitem__(self, *args)
def pop(self):
r"""pop(TravelTimeList_internal self) -> TravelTime"""
return _seismology.TravelTimeList_internal_pop(self)
def append(self, x):
r"""append(TravelTimeList_internal self, TravelTime x)"""
return _seismology.TravelTimeList_internal_append(self, x)
def empty(self):
r"""empty(TravelTimeList_internal self) -> bool"""
return _seismology.TravelTimeList_internal_empty(self)
def size(self):
r"""size(TravelTimeList_internal self) -> std::list< Seiscomp::TravelTime >::size_type"""
return _seismology.TravelTimeList_internal_size(self)
def swap(self, v):
r"""swap(TravelTimeList_internal self, TravelTimeList_internal v)"""
return _seismology.TravelTimeList_internal_swap(self, v)
def begin(self):
r"""begin(TravelTimeList_internal self) -> std::list< Seiscomp::TravelTime >::iterator"""
return _seismology.TravelTimeList_internal_begin(self)
def end(self):
r"""end(TravelTimeList_internal self) -> std::list< Seiscomp::TravelTime >::iterator"""
return _seismology.TravelTimeList_internal_end(self)
def rbegin(self):
r"""rbegin(TravelTimeList_internal self) -> std::list< Seiscomp::TravelTime >::reverse_iterator"""
return _seismology.TravelTimeList_internal_rbegin(self)
def rend(self):
r"""rend(TravelTimeList_internal self) -> std::list< Seiscomp::TravelTime >::reverse_iterator"""
return _seismology.TravelTimeList_internal_rend(self)
def clear(self):
r"""clear(TravelTimeList_internal self)"""
return _seismology.TravelTimeList_internal_clear(self)
def get_allocator(self):
r"""get_allocator(TravelTimeList_internal self) -> std::list< Seiscomp::TravelTime >::allocator_type"""
return _seismology.TravelTimeList_internal_get_allocator(self)
def pop_back(self):
r"""pop_back(TravelTimeList_internal self)"""
return _seismology.TravelTimeList_internal_pop_back(self)
def erase(self, *args):
r"""
erase(TravelTimeList_internal self, std::list< Seiscomp::TravelTime >::iterator pos) -> std::list< Seiscomp::TravelTime >::iterator
erase(TravelTimeList_internal self, std::list< Seiscomp::TravelTime >::iterator first, std::list< Seiscomp::TravelTime >::iterator last) -> std::list< Seiscomp::TravelTime >::iterator
"""
return _seismology.TravelTimeList_internal_erase(self, *args)
def __init__(self, *args):
r"""
__init__(TravelTimeList_internal self) -> TravelTimeList_internal
__init__(TravelTimeList_internal self, TravelTimeList_internal other) -> TravelTimeList_internal
__init__(TravelTimeList_internal self, std::list< Seiscomp::TravelTime >::size_type size) -> TravelTimeList_internal
__init__(TravelTimeList_internal self, std::list< Seiscomp::TravelTime >::size_type size, TravelTime value) -> TravelTimeList_internal
"""
_seismology.TravelTimeList_internal_swiginit(self, _seismology.new_TravelTimeList_internal(*args))
def push_back(self, x):
r"""push_back(TravelTimeList_internal self, TravelTime x)"""
return _seismology.TravelTimeList_internal_push_back(self, x)
def front(self):
r"""front(TravelTimeList_internal self) -> TravelTime"""
return _seismology.TravelTimeList_internal_front(self)
def back(self):
r"""back(TravelTimeList_internal self) -> TravelTime"""
return _seismology.TravelTimeList_internal_back(self)
def assign(self, n, x):
r"""assign(TravelTimeList_internal self, std::list< Seiscomp::TravelTime >::size_type n, TravelTime x)"""
return _seismology.TravelTimeList_internal_assign(self, n, x)
def resize(self, *args):
r"""
resize(TravelTimeList_internal self, std::list< Seiscomp::TravelTime >::size_type new_size)
resize(TravelTimeList_internal self, std::list< Seiscomp::TravelTime >::size_type new_size, TravelTime x)
"""
return _seismology.TravelTimeList_internal_resize(self, *args)
def insert(self, *args):
r"""
insert(TravelTimeList_internal self, std::list< Seiscomp::TravelTime >::iterator pos, TravelTime x) -> std::list< Seiscomp::TravelTime >::iterator
insert(TravelTimeList_internal self, std::list< Seiscomp::TravelTime >::iterator pos, std::list< Seiscomp::TravelTime >::size_type n, TravelTime x)
"""
return _seismology.TravelTimeList_internal_insert(self, *args)
def pop_front(self):
r"""pop_front(TravelTimeList_internal self)"""
return _seismology.TravelTimeList_internal_pop_front(self)
def push_front(self, x):
r"""push_front(TravelTimeList_internal self, TravelTime x)"""
return _seismology.TravelTimeList_internal_push_front(self, x)
def reverse(self):
r"""reverse(TravelTimeList_internal self)"""
return _seismology.TravelTimeList_internal_reverse(self)
__swig_destroy__ = _seismology.delete_TravelTimeList_internal
# Register TravelTimeList_internal in _seismology:
_seismology.TravelTimeList_internal_swigregister(TravelTimeList_internal)
class Regions(object):
r"""Proxy of C++ Seiscomp::Regions class."""
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")
__repr__ = _swig_repr
@staticmethod
def getFlinnEngdahlRegion(lat, lon):
r"""getFlinnEngdahlRegion(double lat, double lon) -> std::string"""
return _seismology.Regions_getFlinnEngdahlRegion(lat, lon)
@staticmethod
def getFlinnEngdahlRegionsCount():
r"""getFlinnEngdahlRegionsCount() -> int"""
return _seismology.Regions_getFlinnEngdahlRegionsCount()
@staticmethod
def getFlinnEngdahlRegionById(id):
r"""getFlinnEngdahlRegionById(int id) -> std::string"""
return _seismology.Regions_getFlinnEngdahlRegionById(id)
@staticmethod
def load():
r"""load()"""
return _seismology.Regions_load()
@staticmethod
def getRegionName(lat, lon):
r"""getRegionName(double lat, double lon) -> std::string"""
return _seismology.Regions_getRegionName(lat, lon)
@staticmethod
def polyRegions():
r"""polyRegions() -> Seiscomp::Geo::PolyRegions &"""
return _seismology.Regions_polyRegions()
__swig_destroy__ = _seismology.delete_Regions
# Register Regions in _seismology:
_seismology.Regions_swigregister(Regions)
def Regions_getFlinnEngdahlRegion(lat, lon):
r"""Regions_getFlinnEngdahlRegion(double lat, double lon) -> std::string"""
return _seismology.Regions_getFlinnEngdahlRegion(lat, lon)
def Regions_getFlinnEngdahlRegionsCount():
r"""Regions_getFlinnEngdahlRegionsCount() -> int"""
return _seismology.Regions_getFlinnEngdahlRegionsCount()
def Regions_getFlinnEngdahlRegionById(id):
r"""Regions_getFlinnEngdahlRegionById(int id) -> std::string"""
return _seismology.Regions_getFlinnEngdahlRegionById(id)
def Regions_load():
r"""Regions_load()"""
return _seismology.Regions_load()
def Regions_getRegionName(lat, lon):
r"""Regions_getRegionName(double lat, double lon) -> std::string"""
return _seismology.Regions_getRegionName(lat, lon)
def Regions_polyRegions():
r"""Regions_polyRegions() -> Seiscomp::Geo::PolyRegions &"""
return _seismology.Regions_polyRegions()
SC3_LOCATOR_INTERFACE_VERSION = _seismology.SC3_LOCATOR_INTERFACE_VERSION
class SensorLocationDelegate(seiscomp.core.BaseObject):
r"""Proxy of C++ Seiscomp::Seismology::SensorLocationDelegate class."""
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
def getSensorLocation(self, pick):
r"""getSensorLocation(SensorLocationDelegate self, Pick pick) -> SensorLocation"""
return _seismology.SensorLocationDelegate_getSensorLocation(self, pick)
__swig_destroy__ = _seismology.delete_SensorLocationDelegate
# Register SensorLocationDelegate in _seismology:
_seismology.SensorLocationDelegate_swigregister(SensorLocationDelegate)
class LocatorInterface(seiscomp.core.BaseObject):
r"""Proxy of C++ Seiscomp::Seismology::LocatorInterface class."""
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
F_NONE = _seismology.LocatorInterface_F_NONE
F_BACKAZIMUTH = _seismology.LocatorInterface_F_BACKAZIMUTH
F_SLOWNESS = _seismology.LocatorInterface_F_SLOWNESS
F_TIME = _seismology.LocatorInterface_F_TIME
F_ALL = _seismology.LocatorInterface_F_ALL
EFlagsQuantity = _seismology.LocatorInterface_EFlagsQuantity
NoCapability = _seismology.LocatorInterface_NoCapability
InitialLocation = _seismology.LocatorInterface_InitialLocation
FixedDepth = _seismology.LocatorInterface_FixedDepth
DistanceCutOff = _seismology.LocatorInterface_DistanceCutOff
IgnoreInitialLocation = _seismology.LocatorInterface_IgnoreInitialLocation
CapQuantity = _seismology.LocatorInterface_CapQuantity
Log = _seismology.LocatorInterface_Log
Warning = _seismology.LocatorInterface_Warning
__swig_destroy__ = _seismology.delete_LocatorInterface
@staticmethod
def Create(algo):
r"""Create(char const * algo) -> LocatorInterface"""
return _seismology.LocatorInterface_Create(algo)
def name(self):
r"""name(LocatorInterface self) -> std::string const &"""
return _seismology.LocatorInterface_name(self)
def setSensorLocationDelegate(self, delegate):
r"""setSensorLocationDelegate(LocatorInterface self, SensorLocationDelegate delegate)"""
return _seismology.LocatorInterface_setSensorLocationDelegate(self, delegate)
def init(self, config):
r"""init(LocatorInterface self, Config config) -> bool"""
return _seismology.LocatorInterface_init(self, config)
def parameters(self):
r"""parameters(LocatorInterface self) -> VectorStr"""
return _seismology.LocatorInterface_parameters(self)
def parameter(self, name):
r"""parameter(LocatorInterface self, std::string const & name) -> std::string"""
return _seismology.LocatorInterface_parameter(self, name)
def setParameter(self, name, value):
r"""setParameter(LocatorInterface self, std::string const & name, std::string const & value) -> bool"""
return _seismology.LocatorInterface_setParameter(self, name, value)
def profiles(self):
r"""profiles(LocatorInterface self) -> VectorStr"""
return _seismology.LocatorInterface_profiles(self)
def setProfile(self, name):
r"""setProfile(LocatorInterface self, std::string const & name)"""
return _seismology.LocatorInterface_setProfile(self, name)
def capabilities(self):
r"""capabilities(LocatorInterface self) -> int"""
return _seismology.LocatorInterface_capabilities(self)
def locate(self, *args):
r"""
locate(LocatorInterface self, Seiscomp::Seismology::LocatorInterface::PickList & pickList) -> Origin
locate(LocatorInterface self, Seiscomp::Seismology::LocatorInterface::PickList & pickList, double initLat, double initLon, double initDepth, Time initTime) -> Origin
"""
return _seismology.LocatorInterface_locate(self, *args)
def relocate(self, origin):
r"""relocate(LocatorInterface self, Origin origin) -> Origin"""
return _seismology.LocatorInterface_relocate(self, origin)
def lastMessage(self, arg2):
r"""lastMessage(LocatorInterface self, Seiscomp::Seismology::LocatorInterface::MessageType arg2) -> std::string"""
return _seismology.LocatorInterface_lastMessage(self, arg2)
def supports(self, arg2):
r"""supports(LocatorInterface self, Seiscomp::Seismology::LocatorInterface::Capability arg2) -> bool"""
return _seismology.LocatorInterface_supports(self, arg2)
def setFixedDepth(self, depth, use=True):
r"""setFixedDepth(LocatorInterface self, double depth, bool use=True)"""
return _seismology.LocatorInterface_setFixedDepth(self, depth, use)
def useFixedDepth(self, use=True):
r"""useFixedDepth(LocatorInterface self, bool use=True)"""
return _seismology.LocatorInterface_useFixedDepth(self, use)
def fixedDepth(self):
r"""fixedDepth(LocatorInterface self) -> double"""
return _seismology.LocatorInterface_fixedDepth(self)
def usingFixedDepth(self):
r"""usingFixedDepth(LocatorInterface self) -> bool"""
return _seismology.LocatorInterface_usingFixedDepth(self)
def releaseDepth(self):
r"""releaseDepth(LocatorInterface self)"""
return _seismology.LocatorInterface_releaseDepth(self)
def setDistanceCutOff(self, distance):
r"""setDistanceCutOff(LocatorInterface self, double distance)"""
return _seismology.LocatorInterface_setDistanceCutOff(self, distance)
def releaseDistanceCutOff(self):
r"""releaseDistanceCutOff(LocatorInterface self)"""
return _seismology.LocatorInterface_releaseDistanceCutOff(self)
def isInitialLocationIgnored(self):
r"""isInitialLocationIgnored(LocatorInterface self) -> bool"""
return _seismology.LocatorInterface_isInitialLocationIgnored(self)
def setIgnoreInitialLocation(self, f):
r"""setIgnoreInitialLocation(LocatorInterface self, bool f)"""
return _seismology.LocatorInterface_setIgnoreInitialLocation(self, f)
def getPick(self, arrival):
r"""getPick(LocatorInterface self, Arrival arrival) -> Pick"""
return _seismology.LocatorInterface_getPick(self, arrival)
def getSensorLocation(self, pick):
r"""getSensorLocation(LocatorInterface self, Pick pick) -> SensorLocation"""
return _seismology.LocatorInterface_getSensorLocation(self, pick)
# Register LocatorInterface in _seismology:
_seismology.LocatorInterface_swigregister(LocatorInterface)
def LocatorInterface_Create(algo):
r"""LocatorInterface_Create(char const * algo) -> LocatorInterface"""
return _seismology.LocatorInterface_Create(algo)
class PickNotFoundException(seiscomp.core.GeneralException):
r"""Proxy of C++ Seiscomp::Seismology::PickNotFoundException class."""
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):
r"""
__init__(PickNotFoundException self) -> PickNotFoundException
__init__(PickNotFoundException self, std::string const & str) -> PickNotFoundException
"""
_seismology.PickNotFoundException_swiginit(self, _seismology.new_PickNotFoundException(*args))
__swig_destroy__ = _seismology.delete_PickNotFoundException
# Register PickNotFoundException in _seismology:
_seismology.PickNotFoundException_swigregister(PickNotFoundException)
class LocatorException(seiscomp.core.GeneralException):
r"""Proxy of C++ Seiscomp::Seismology::LocatorException class."""
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):
r"""
__init__(LocatorException self) -> LocatorException
__init__(LocatorException self, std::string const & str) -> LocatorException
"""
_seismology.LocatorException_swiginit(self, _seismology.new_LocatorException(*args))
__swig_destroy__ = _seismology.delete_LocatorException
# Register LocatorException in _seismology:
_seismology.LocatorException_swigregister(LocatorException)
class StationNotFoundException(seiscomp.core.GeneralException):
r"""Proxy of C++ Seiscomp::Seismology::StationNotFoundException class."""
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):
r"""
__init__(StationNotFoundException self) -> StationNotFoundException
__init__(StationNotFoundException self, std::string const & str) -> StationNotFoundException
"""
_seismology.StationNotFoundException_swiginit(self, _seismology.new_StationNotFoundException(*args))
__swig_destroy__ = _seismology.delete_StationNotFoundException
# Register StationNotFoundException in _seismology:
_seismology.StationNotFoundException_swigregister(StationNotFoundException)
def arrivalToFlags(arrival):
r"""arrivalToFlags(Arrival arrival) -> int"""
return _seismology.arrivalToFlags(arrival)
def flagsToArrival(arrival, flags):
r"""flagsToArrival(Arrival arrival, int flags)"""
return _seismology.flagsToArrival(arrival, flags)
class FileNotFoundError(seiscomp.core.GeneralException):
r"""Proxy of C++ Seiscomp::FileNotFoundError class."""
thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
__repr__ = _swig_repr
def __init__(self, filename):
r"""__init__(FileNotFoundError self, std::string const & filename) -> FileNotFoundError"""
_seismology.FileNotFoundError_swiginit(self, _seismology.new_FileNotFoundError(filename))
__swig_destroy__ = _seismology.delete_FileNotFoundError
# Register FileNotFoundError in _seismology:
_seismology.FileNotFoundError_swigregister(FileNotFoundError)
class MultipleModelsError(seiscomp.core.GeneralException):
r"""Proxy of C++ Seiscomp::MultipleModelsError class."""
thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
__repr__ = _swig_repr
def __init__(self, model):
r"""__init__(MultipleModelsError self, std::string const & model) -> MultipleModelsError"""
_seismology.MultipleModelsError_swiginit(self, _seismology.new_MultipleModelsError(model))
__swig_destroy__ = _seismology.delete_MultipleModelsError
# Register MultipleModelsError in _seismology:
_seismology.MultipleModelsError_swigregister(MultipleModelsError)
class NoPhaseError(seiscomp.core.GeneralException):
r"""Proxy of C++ Seiscomp::NoPhaseError class."""
thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
__repr__ = _swig_repr
def __init__(self):
r"""__init__(NoPhaseError self) -> NoPhaseError"""
_seismology.NoPhaseError_swiginit(self, _seismology.new_NoPhaseError())
__swig_destroy__ = _seismology.delete_NoPhaseError
# Register NoPhaseError in _seismology:
_seismology.NoPhaseError_swigregister(NoPhaseError)
class TravelTime(object):
r"""Proxy of C++ Seiscomp::TravelTime class."""
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):
r"""
__init__(TravelTime self) -> TravelTime
__init__(TravelTime self, std::string const & _phase, double _time, double _dtdd, double _dtdh, double _dddp, double _takeoff) -> TravelTime
"""
_seismology.TravelTime_swiginit(self, _seismology.new_TravelTime(*args))
def __eq__(self, other):
r"""__eq__(TravelTime self, TravelTime other) -> bool"""
return _seismology.TravelTime___eq__(self, other)
def __lt__(self, other):
r"""__lt__(TravelTime self, TravelTime other) -> bool"""
return _seismology.TravelTime___lt__(self, other)
phase = property(_seismology.TravelTime_phase_get, _seismology.TravelTime_phase_set, doc=r"""phase : std::string""")
time = property(_seismology.TravelTime_time_get, _seismology.TravelTime_time_set, doc=r"""time : double""")
dtdd = property(_seismology.TravelTime_dtdd_get, _seismology.TravelTime_dtdd_set, doc=r"""dtdd : double""")
dtdh = property(_seismology.TravelTime_dtdh_get, _seismology.TravelTime_dtdh_set, doc=r"""dtdh : double""")
dddp = property(_seismology.TravelTime_dddp_get, _seismology.TravelTime_dddp_set, doc=r"""dddp : double""")
takeoff = property(_seismology.TravelTime_takeoff_get, _seismology.TravelTime_takeoff_set, doc=r"""takeoff : double""")
azi = property(_seismology.TravelTime_azi_get, _seismology.TravelTime_azi_set, doc=r"""azi : Seiscomp::Core::Optional<(double)>::Impl""")
__swig_destroy__ = _seismology.delete_TravelTime
# Register TravelTime in _seismology:
_seismology.TravelTime_swigregister(TravelTime)
class TravelTimeList(TravelTimeList_internal):
r"""Proxy of C++ Seiscomp::TravelTimeList class."""
thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
__repr__ = _swig_repr
def isEmpty(self):
r"""isEmpty(TravelTimeList self) -> bool"""
return _seismology.TravelTimeList_isEmpty(self)
def sortByTime(self):
r"""sortByTime(TravelTimeList self)"""
return _seismology.TravelTimeList_sortByTime(self)
depth = property(_seismology.TravelTimeList_depth_get, _seismology.TravelTimeList_depth_set, doc=r"""depth : double""")
delta = property(_seismology.TravelTimeList_delta_get, _seismology.TravelTimeList_delta_set, doc=r"""delta : double""")
def __init__(self):
r"""__init__(TravelTimeList self) -> TravelTimeList"""
_seismology.TravelTimeList_swiginit(self, _seismology.new_TravelTimeList())
__swig_destroy__ = _seismology.delete_TravelTimeList
# Register TravelTimeList in _seismology:
_seismology.TravelTimeList_swigregister(TravelTimeList)
class TravelTimeTableInterface(seiscomp.core.BaseObject):
r"""Proxy of C++ Seiscomp::TravelTimeTableInterface class."""
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__ = _seismology.delete_TravelTimeTableInterface
@staticmethod
def Create(name):
r"""Create(char const * name) -> TravelTimeTableInterface"""
return _seismology.TravelTimeTableInterface_Create(name)
def setModel(self, model):
r"""setModel(TravelTimeTableInterface self, std::string const & model) -> bool"""
return _seismology.TravelTimeTableInterface_setModel(self, model)
def model(self):
r"""model(TravelTimeTableInterface self) -> std::string const &"""
return _seismology.TravelTimeTableInterface_model(self)
def compute(self, *args):
r"""
compute(TravelTimeTableInterface self, double lat1, double lon1, double dep1, double lat2, double lon2, double elev2=0., int ellc=1) -> TravelTimeList
compute(TravelTimeTableInterface self, char const * phase, double lat1, double lon1, double dep1, double lat2, double lon2, double elev2=0., int ellc=1) -> TravelTime
"""
return _seismology.TravelTimeTableInterface_compute(self, *args)
def computeFirst(self, lat1, lon1, dep1, lat2, lon2, elev2=0., ellc=1):
r"""computeFirst(TravelTimeTableInterface self, double lat1, double lon1, double dep1, double lat2, double lon2, double elev2=0., int ellc=1) -> TravelTime"""
return _seismology.TravelTimeTableInterface_computeFirst(self, lat1, lon1, dep1, lat2, lon2, elev2, ellc)
def computeTime(self, phase, lat1, lon1, dep1, lat2, lon2, elev2=0., ellc=1):
r"""computeTime(TravelTimeTableInterface self, char const * phase, double lat1, double lon1, double dep1, double lat2, double lon2, double elev2=0., int ellc=1) -> double"""
return _seismology.TravelTimeTableInterface_computeTime(self, phase, lat1, lon1, dep1, lat2, lon2, elev2, ellc)
# Register TravelTimeTableInterface in _seismology:
_seismology.TravelTimeTableInterface_swigregister(TravelTimeTableInterface)
def TravelTimeTableInterface_Create(name):
r"""TravelTimeTableInterface_Create(char const * name) -> TravelTimeTableInterface"""
return _seismology.TravelTimeTableInterface_Create(name)
class TravelTimeTable(TravelTimeTableInterface):
r"""Proxy of C++ Seiscomp::TravelTimeTable class."""
thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
__repr__ = _swig_repr
def __init__(self):
r"""__init__(TravelTimeTable self) -> TravelTimeTable"""
_seismology.TravelTimeTable_swiginit(self, _seismology.new_TravelTimeTable())
def setModel(self, model):
r"""setModel(TravelTimeTable self, std::string const & model) -> bool"""
return _seismology.TravelTimeTable_setModel(self, model)
def model(self):
r"""model(TravelTimeTable self) -> std::string const &"""
return _seismology.TravelTimeTable_model(self)
def compute(self, *args):
r"""
compute(TravelTimeTable self, double lat1, double lon1, double dep1, double lat2, double lon2, double elev2=0., int ellc=1) -> TravelTimeList
compute(TravelTimeTable self, char const * phase, double lat1, double lon1, double dep1, double lat2, double lon2, double elev2=0., int ellc=1) -> TravelTime
"""
return _seismology.TravelTimeTable_compute(self, *args)
def computeFirst(self, lat1, lon1, dep1, lat2, lon2, elev2=0., ellc=1):
r"""computeFirst(TravelTimeTable self, double lat1, double lon1, double dep1, double lat2, double lon2, double elev2=0., int ellc=1) -> TravelTime"""
return _seismology.TravelTimeTable_computeFirst(self, lat1, lon1, dep1, lat2, lon2, elev2, ellc)
__swig_destroy__ = _seismology.delete_TravelTimeTable
# Register TravelTimeTable in _seismology:
_seismology.TravelTimeTable_swigregister(TravelTimeTable)
def ellipcorr(phase, lat1, lon1, lat2, lon2, depth, corr):
r"""ellipcorr(std::string const & phase, double lat1, double lon1, double lat2, double lon2, double depth, double & corr) -> bool"""
return _seismology.ellipcorr(phase, lat1, lon1, lat2, lon2, depth, corr)
def getPhase(arg1, phaseCode):
r"""getPhase(TravelTimeList arg1, std::string const & phaseCode) -> TravelTime"""
return _seismology.getPhase(arg1, phaseCode)
def firstArrivalP(arg1):
r"""firstArrivalP(TravelTimeList arg1) -> TravelTime"""
return _seismology.firstArrivalP(arg1)
class LocSATErrorEllipsoid(object):
r"""Proxy of C++ Seiscomp::LocSATErrorEllipsoid class."""
thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
__repr__ = _swig_repr
def __init__(self):
r"""__init__(LocSATErrorEllipsoid self) -> LocSATErrorEllipsoid"""
_seismology.LocSATErrorEllipsoid_swiginit(self, _seismology.new_LocSATErrorEllipsoid())
sxx = property(_seismology.LocSATErrorEllipsoid_sxx_get, _seismology.LocSATErrorEllipsoid_sxx_set, doc=r"""sxx : float""")
syy = property(_seismology.LocSATErrorEllipsoid_syy_get, _seismology.LocSATErrorEllipsoid_syy_set, doc=r"""syy : float""")
szz = property(_seismology.LocSATErrorEllipsoid_szz_get, _seismology.LocSATErrorEllipsoid_szz_set, doc=r"""szz : float""")
stt = property(_seismology.LocSATErrorEllipsoid_stt_get, _seismology.LocSATErrorEllipsoid_stt_set, doc=r"""stt : float""")
sxy = property(_seismology.LocSATErrorEllipsoid_sxy_get, _seismology.LocSATErrorEllipsoid_sxy_set, doc=r"""sxy : float""")
sxz = property(_seismology.LocSATErrorEllipsoid_sxz_get, _seismology.LocSATErrorEllipsoid_sxz_set, doc=r"""sxz : float""")
syz = property(_seismology.LocSATErrorEllipsoid_syz_get, _seismology.LocSATErrorEllipsoid_syz_set, doc=r"""syz : float""")
stx = property(_seismology.LocSATErrorEllipsoid_stx_get, _seismology.LocSATErrorEllipsoid_stx_set, doc=r"""stx : float""")
sty = property(_seismology.LocSATErrorEllipsoid_sty_get, _seismology.LocSATErrorEllipsoid_sty_set, doc=r"""sty : float""")
stz = property(_seismology.LocSATErrorEllipsoid_stz_get, _seismology.LocSATErrorEllipsoid_stz_set, doc=r"""stz : float""")
sdobs = property(_seismology.LocSATErrorEllipsoid_sdobs_get, _seismology.LocSATErrorEllipsoid_sdobs_set, doc=r"""sdobs : float""")
smajax = property(_seismology.LocSATErrorEllipsoid_smajax_get, _seismology.LocSATErrorEllipsoid_smajax_set, doc=r"""smajax : float""")
sminax = property(_seismology.LocSATErrorEllipsoid_sminax_get, _seismology.LocSATErrorEllipsoid_sminax_set, doc=r"""sminax : float""")
strike = property(_seismology.LocSATErrorEllipsoid_strike_get, _seismology.LocSATErrorEllipsoid_strike_set, doc=r"""strike : float""")
sdepth = property(_seismology.LocSATErrorEllipsoid_sdepth_get, _seismology.LocSATErrorEllipsoid_sdepth_set, doc=r"""sdepth : float""")
stime = property(_seismology.LocSATErrorEllipsoid_stime_get, _seismology.LocSATErrorEllipsoid_stime_set, doc=r"""stime : float""")
conf = property(_seismology.LocSATErrorEllipsoid_conf_get, _seismology.LocSATErrorEllipsoid_conf_set, doc=r"""conf : float""")
__swig_destroy__ = _seismology.delete_LocSATErrorEllipsoid
# Register LocSATErrorEllipsoid in _seismology:
_seismology.LocSATErrorEllipsoid_swigregister(LocSATErrorEllipsoid)
class LocSAT(LocatorInterface):
r"""Proxy of C++ Seiscomp::LocSAT class."""
thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
__repr__ = _swig_repr
def __init__(self):
r"""__init__(LocSAT self) -> LocSAT"""
_seismology.LocSAT_swiginit(self, _seismology.new_LocSAT())
__swig_destroy__ = _seismology.delete_LocSAT
def init(self, config):
r"""init(LocSAT self, Config config) -> bool"""
return _seismology.LocSAT_init(self, config)
def parameters(self):
r"""parameters(LocSAT self) -> VectorStr"""
return _seismology.LocSAT_parameters(self)
def parameter(self, name):
r"""parameter(LocSAT self, std::string const & name) -> std::string"""
return _seismology.LocSAT_parameter(self, name)
def setParameter(self, name, value):
r"""setParameter(LocSAT self, std::string const & name, std::string const & value) -> bool"""
return _seismology.LocSAT_setParameter(self, name, value)
def profiles(self):
r"""profiles(LocSAT self) -> VectorStr"""
return _seismology.LocSAT_profiles(self)
def setProfile(self, name):
r"""setProfile(LocSAT self, std::string const & name)"""
return _seismology.LocSAT_setProfile(self, name)
@staticmethod
def setDefaultProfile(name):
r"""setDefaultProfile(std::string const & name)"""
return _seismology.LocSAT_setDefaultProfile(name)
@staticmethod
def currentDefaultProfile():
r"""currentDefaultProfile() -> std::string"""
return _seismology.LocSAT_currentDefaultProfile()
def setNewOriginID(self, newOriginID):
r"""setNewOriginID(LocSAT self, std::string const & newOriginID)"""
return _seismology.LocSAT_setNewOriginID(self, newOriginID)
def capabilities(self):
r"""capabilities(LocSAT self) -> int"""
return _seismology.LocSAT_capabilities(self)
def locate(self, *args):
r"""
locate(LocSAT self, Seiscomp::Seismology::LocatorInterface::PickList & pickList) -> Origin
locate(LocSAT self, Seiscomp::Seismology::LocatorInterface::PickList & pickList, double initLat, double initLon, double initDepth, Time initTime) -> Origin
"""
return _seismology.LocSAT_locate(self, *args)
def relocate(self, origin):
r"""relocate(LocSAT self, Origin origin) -> Origin"""
return _seismology.LocSAT_relocate(self, origin)
def errorEllipsoid(self):
r"""errorEllipsoid(LocSAT self) -> LocSATErrorEllipsoid"""
return _seismology.LocSAT_errorEllipsoid(self)
# Register LocSAT in _seismology:
_seismology.LocSAT_swigregister(LocSAT)
def LocSAT_setDefaultProfile(name):
r"""LocSAT_setDefaultProfile(std::string const & name)"""
return _seismology.LocSAT_setDefaultProfile(name)
def LocSAT_currentDefaultProfile():
r"""LocSAT_currentDefaultProfile() -> std::string"""
return _seismology.LocSAT_currentDefaultProfile()

View File

@ -0,0 +1,509 @@
############################################################################
# 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. #
############################################################################
import os
import sys
import glob
import getpass
try:
# Python 2.5
from xml.etree import ElementTree
from xml.parsers.expat import ExpatError as ParseError
except ImportError:
from elementtree import ElementTree
from xml.parsers.expat import ExpatError as ParseError
from seiscomp import config
# Python version depended string conversion
if sys.version_info[0] < 3:
py3input = raw_input #pylint: disable=E0602
else:
py3input = input
def tagname(element):
names = element.tag.split("}")
if len(names) == 0:
return ""
return names.pop()
def oneliner(txt):
return txt.strip().replace("\n", "")
def block(txt, width=80):
lines = [l.strip() for l in txt.strip().replace("\r", "").split('\n')]
line = "\n".join(lines)
current = 0
lines = []
while current < len(line):
end = line.find('\n', current)
if (end == -1) or (end - current > width):
if len(line) - current > width:
end = line.rfind(' ', current, current+width)
if end == -1:
end = line.find(' ', current)
if end == -1:
end = len(line)
else:
end = len(line)
lines.append(line[current:end].strip())
current = end + 1
return lines
class SetupNode:
def __init__(self, parent, inp, next = None):
self.parent = parent
self.next = next
self.child = None
self.activeChild = None
self.modname = ""
self.groupname = ""
self.input = inp
self.value = ""
self.path = ""
self.optionValue = None
self.isOption = False
class Option:
"""
Setup input option wrapper.
"""
def __init__(self, value):
self.value = value
self.desc = None
self.inputs = []
class Input:
"""
Setup input wrapper.
"""
def __init__(self, name, t, default_value=None):
self.name = name
self.type = t
self.default_value = default_value
self.text = None
self.desc = None
self.echo = None
self.options = []
def dumpTree(cfg, node):
if node.input:
cfg.setString(node.modname + "." + node.path, node.value)
if node.activeChild:
if node.isOption:
dumpTree(cfg, node.activeChild.child)
else:
dumpTree(cfg, node.activeChild)
if not node.next is None:
dumpTree(cfg, node.next)
class Simple:
"""
Simple console setup handler that parses all description xml files
and extracts the setup part. It asks for all available setting line
by line and passes the resulting configuration back which is then
passed to all init modules that have a setup method.
"""
def __init__(self, args = []):
self.modules = args
self.setupTree = SetupNode(None, None)
self.paths = []
self.currentNode = None
def run(self, env):
desc_pattern = os.path.join(
env.SEISCOMP_ROOT, "etc", "descriptions", "*.xml")
xmls = glob.glob(desc_pattern)
setup_groups = {}
for f in xmls:
try:
tree = ElementTree.parse(f)
except ParseError as xxx_todo_changeme:
(err) = xxx_todo_changeme
sys.stderr.write("%s: parsing XML failed: %s\n" % (f, err))
continue
root = tree.getroot()
if tagname(root) != "seiscomp":
sys.stderr.write(
"%s: wrong root tag, expected 'seiscomp'\n" % f)
continue
# Read all modules
mods = tree.findall("module")
for mod in mods:
modname = mod.get('name')
if not modname:
sys.stderr.write("%s: skipping module without name\n" % f)
continue
if modname in setup_groups:
raise Exception(
"%s: duplicate module name: %s" % (f, modname))
if self.modules and modname not in self.modules:
continue
setup = mod.find("setup")
if setup is None:
continue
groups = setup.findall("group")
if len(groups) == 0:
continue
setup_groups[modname] = groups
# Read all plugin's
plugins = tree.findall("plugin")
for plugin in plugins:
try:
modname = plugin.find('extends').text.strip()
except:
raise Exception("%s: plugin does not define 'extends'" % f)
if modname.find('\n') >= 0:
raise Exception("%s: wrong module name in plugin." \
"extends: no newlines allowed" % f)
if not modname:
sys.stderr.write("%s: skipping module without name\n" % f)
continue
setup = plugin.find("setup")
if setup is None:
continue
groups = setup.findall("group")
if len(groups) == 0:
continue
if modname in setup_groups:
setup_groups[modname] += groups
else:
setup_groups[modname] = groups
for name, groups in sorted(setup_groups.items()):
self.addGroups(self.setupTree, name, groups)
# Always descend to the first child (if available)
self.setupTree.activeChild = self.setupTree.child
self.currentNode = self.setupTree.activeChild
sys.stdout.write('''
====================================================================
SeisComP setup
====================================================================
This initializes the configuration of your installation.
If you already made adjustments to the configuration files
be warned that this setup will overwrite existing parameters
with default values. This is not a configurator for all
options of your setup but helps to setup initial standard values.
--------------------------------------------------------------------
Hint: Entered values starting with a dot (.) are handled
as commands. Available commands are:
quit: Quit setup without modification to your configuration.
back: Go back to the previous parameter.
help: Show help about the current parameter (if available).
If you need to enter a value with a leading dot, escape it
with backslash, e.g. "\\.value".
--------------------------------------------------------------------
''')
try:
self.fillTree()
except StopIteration:
raise Exception("aborted by user")
cfg = config.Config()
dumpTree(cfg, self.setupTree)
return cfg
def addGroups(self, node, modname, groups):
for g in groups:
self.addInputs(None, node, modname, g.get(
'name'), g, g.get('name', "") + ".")
def addInputs(self, obj, parent, modname, group, xml, prefix):
childs = parent.child;
if not childs is None:
while not childs.next is None:
childs = childs.next
inputs = xml.findall("input")
for inp in inputs:
name = inp.get('name')
if not name:
raise Exception("%s: no name defined" % prefix)
input_ = Input(name, inp.get('type'), inp.get('default'))
try:
input_.text = oneliner(inp.find('text').text)
except Exception:
input_.text = input_.name
try:
input_.desc = block(inp.find('description').text)
except Exception:
pass
input_.echo = inp.get('echo')
if obj:
obj.inputs.append(input_)
opts = inp.findall("option")
node = SetupNode(parent, input_)
node.path = prefix + input_.name
node.value = input_.default_value
node.modname = modname
node.groupname = group
node.isOption = len(opts) > 0
if childs is None:
childs = node
parent.child = childs
else:
childs.next = node
childs = childs.next;
options = node.child
for opt in opts:
value = opt.get('value')
if not value:
raise Exception("%s: option without value" % prefix)
optionNode = SetupNode(node, input_)
optionNode.path = node.path + "." + value
optionNode.modname = modname
optionNode.groupname = group
optionNode.isOption = False
optionNode.optionValue = value
option = Option(value)
try:
option.desc = block(opt.find('description').text, 74)
except Exception:
pass
input_.options.append(option)
if options is None:
options = optionNode
node.child = options
else:
options.next = optionNode
options = options.next
self.addInputs(option, optionNode, modname,
group, opt, node.path + ".")
def fillTree(self):
while True:
if not self.currentNode:
sys.stdout.write("\nFinished setup\n--------------\n\n")
sys.stdout.write("P) Proceed to apply configuration\n")
sys.stdout.write("D) Dump entered parameters\n")
sys.stdout.write("B) Back to last parameter\n")
sys.stdout.write("Q) Quit without changes\n")
value = py3input('Command? [P]: ').upper()
if value == "Q":
raise StopIteration()
if value == "D":
sys.stdout.write("\n----\n")
cfg = config.Config()
dumpTree(cfg, self.setupTree)
cfg.writeConfig("-")
sys.stdout.write("----\n\n")
continue
if value == "P" or not value:
sys.stdout.write("\nRunning setup\n-------------\n\n")
return
if value == "B":
self.prevStep()
continue
sys.stdout.write("\nEnter either p, b or q\n")
continue
if not self.currentNode.input:
self.nextStep()
continue
default_value = self.valueToString(self.currentNode)
isChoice = False
isPassword = False
if self.currentNode.input.echo == "password":
isPassword = True
node_text = default_value
prompt = self.currentNode.input.text
if isPassword:
node_text = '*' * len(node_text)
prompt += " (input not echoed)"
if (not self.currentNode.input.type or \
self.currentNode.input.type != "boolean") and \
len(self.currentNode.input.options) > 0:
idx = 0
def_idx = 0
for opt in self.currentNode.input.options:
sys.stdout.write("%2d) %s\n" % (idx, opt.value))
if opt.desc:
for l in opt.desc:
sys.stdout.write(" %s\n" % l)
if default_value == opt.value:
def_idx = idx
idx += 1
isChoice = True
prompt += " [%d]: " % def_idx
else:
prompt += " [%s]: " % node_text
if self.currentNode.input.echo == "password":
value = getpass.getpass(prompt)
else:
value = py3input(prompt)
if not value:
value = default_value
elif value == ".help":
if self.currentNode.input.desc:
sys.stdout.write("\n%s\n\n" %
"\n".join(self.currentNode.input.desc))
else:
sys.stdout.write("\nSorry, no help available.\n\n")
continue
elif value == ".back":
self.prevStep()
continue
elif value == ".quit":
raise StopIteration()
elif value.startswith("."):
sys.stdout.write("Unknown command. Values starting with '.' are handled has commands such as\n"
"'.help', '.quit' or '.back'. To use a leading dot in a value, escape it with '\'\n"
"e.g. '\\.color'\n")
continue
else:
# Replace leading \. with .
if value.startswith('\\.'):
value = value[1:]
if isChoice:
try:
idx = int(value)
except ValueError:
idx = -1
if idx < 0 or idx >= len(self.currentNode.input.options):
sys.stdout.write("\nEnter a number between 0 and %d\n\n" % (
len(self.currentNode.input.options)-1))
continue
value = self.currentNode.input.options[idx].value
if self.currentNode.input.type and self.currentNode.input.type == "boolean":
if not value in ["yes", "no"]:
sys.stdout.write("Please enter 'yes' or 'no'\n")
continue
if value == "yes":
value = "true"
else:
value = "false"
self.currentNode.value = value
self.nextStep()
@staticmethod
def valueToString(node):
if not node.input.type:
if node.value is None:
return ""
return node.value
if node.input.type == "boolean":
if node.value == "true":
return "yes"
if node.value == "false":
return "no"
return "yes"
if node.value is None:
return ""
return node.value
def prevStep(self):
if len(self.paths) == 0:
sys.stdout.write("No previous step available\n")
return
self.currentNode = self.paths.pop()
def nextStep(self):
self.currentNode.activeChild = None
self.paths.append(self.currentNode)
# Choice?
if self.currentNode.isOption:
child = self.currentNode.child
while not child is None:
if child.optionValue == self.currentNode.value:
if not child.child is None:
self.currentNode.activeChild = child
self.currentNode = child.child
return
break
child = child.next
next = self.currentNode.next
while next is None and not self.currentNode.parent is None:
self.currentNode = self.currentNode.parent
if not self.currentNode.optionValue is None:
continue
next = self.currentNode.next
self.currentNode = next

View File

@ -0,0 +1,645 @@
############################################################################
# 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 glob
# Python version depended string conversion
if sys.version_info[0] < 3:
py3input = raw_input #pylint: disable=E0602
else:
py3input = input
def split_tokens(line):
return line.split()
def convert_wildcard(s):
wild = s.split(".")
if len(wild) > 2:
raise Exception("station selector: only one dot allowed")
# Add station wildcard if only network is given
if len(wild) == 1:
wild.append('*')
return '_'.join([x if x else '*' for x in wild])
def convert_stations(s):
toks = s.split(".")
if len(toks) != 2:
raise Exception("station: expected format: NET.STA")
return '_'.join(toks)
class CLI:
"""
Simple console shell.
"""
def __init__(self, env = None):
self.env = env
def run(self, env):
self.env = env
sys.stdout.write('''\
%s
SeisComP shell
%s
Welcome to the SeisComP interactive shell. You can get help about
available commands with 'help'. 'exit' leaves the shell.
''' % (("="*80), ("="*80)))
prompt = "$ "
while True:
line = py3input(prompt).strip()
toks = split_tokens(line)
if len(toks) == 0:
continue
if line in ("exit", "quit"):
break
self.handleCommand(toks[0], toks[1:])
def handleCommand(self, cmd, args):
try:
if cmd == "help":
return self.commandHelp(args)
if cmd == "list":
return self.commandList(args)
if cmd == "delete":
return self.commandDelete(args)
if cmd == "print":
return self.commandPrint(args)
if cmd == "set":
return self.commandSet(args)
if cmd == "remove":
return self.commandRemove(args)
raise Exception("Unknown command: %s" % cmd)
except Exception as e:
sys.stdout.write("%s\n" % str(e))
return False
@staticmethod
def commandHelp(args):
if len(args) == 0:
sys.stdout.write("""\
Commands:
list stations
Lists all available stations keys.
list profiles {mod}
Lists all available profiles of a module.
list modules {sta}
Lists all bound modules of a station incl. profiles (if used).
delete profile {mod} {profile}
Deletes the given profile of given module. If the profile does not exist an
error is raised.
The module is removed from all stations that are using this profile.
delete binding {mod} {sta}
Deletes the binding for given module and station. If the station is bound
to module mod using a profile the binding is kept, removed otherwise.
An existing binding file (etc/key/[mod]/station_[sta]) is deleted in any
case.
print station {sta}
Dumps all set binding parameters for the given station.
set profile {mod} {profile} {sta-sel}
Sets for all selected stations a binding profile of a module.
The resulting station file looks like this:
...
mod:profile
...
This command checks for the existence of the specified profile
set module {mod} {sta-sel}
Binds all selected stations to given module. No profiles are used
and if any of the stations is already using a profile it is removed.
The resulting station key file looks like this:
...
mod
...
remove profile {mod} {profile} {sta-sel}
Removes the binding profile of given module for all selected stations if
module is bound already to that station.
As a result all selected stations that are bound to the given module already
will use a station key file afterwards.
mod:profile -> mod
remove module {mod} {sta-sel}
Unbinds given module from selected stations. The line that refers to the
given module is completely removed from the station key files.
exit
Exit the shell.
quit
Alias for exit.
""")
def commandList(self, args):
if len(args) == 0:
raise Exception("Missing operand")
if args[0] == "stations":
if len(args) > 2:
raise Exception("Too many arguments")
if len(args) > 1:
wild = convert_wildcard(args[1])
else:
wild = "*"
stas = []
for f in sorted(glob.glob(os.path.join(self.env.key_dir, "station_" + wild))):
stas.append(os.path.basename(f)[8:].replace("_", "."))
for s in stas:
print(s)
return True
if args[0] == "profiles":
if len(args) > 2:
raise Exception("Too many arguments")
if len(args) < 2:
raise Exception("Expected: mod")
module = args[1]
for f in sorted(glob.glob(os.path.join(self.env.key_dir, module, "profile_*"))):
print(os.path.basename(f)[8:])
return True
if args[0] == "modules":
if len(args) > 2:
raise Exception("Too many arguments")
if len(args) < 2:
raise Exception("Expected: sta")
sta = convert_stations(args[1])
f = os.path.join(self.env.key_dir, "station_" + sta)
if not os.path.exists(f):
raise Exception("%s: station key does not exists" % args[1])
for l in [line.strip() for line in open(f, "r").readlines()]:
if l.startswith("#"):
continue
if len(l) == 0:
continue
print(l)
return True
raise Exception("Invalid argument: %s" % args[0])
def commandDelete(self, args):
if len(args) == 0:
raise Exception("Missing operand")
if args[0] == "profile":
if len(args) > 3:
raise Exception("Too many arguments")
if len(args) < 3:
raise Exception("Expected: mod profile")
module = args[1]
profile = args[2]
if not os.path.exists(os.path.join(self.env.key_dir, module, "profile_" + profile)):
raise Exception("%s/%s: profile not found" % (module, profile))
os.remove(os.path.join(self.env.key_dir,
module, "profile_" + profile))
modified = 0
for f in glob.glob(os.path.join(self.env.key_dir, "station_*")):
lines = [line.strip() for line in open(f, "r").readlines()]
new_lines = []
is_modified = False
for line in lines:
# Comment line
if line.startswith("#"):
new_lines.append(line)
continue
# Empty line
if not line:
new_lines.append(line)
continue
toks = line.split(':')
# Wrong module name
if toks[0] != module:
new_lines.append(line)
continue
# Profile found
if len(toks) > 1 and toks[1] == profile:
# Filter line
is_modified = True
continue
new_lines.append(line)
if is_modified:
modified += 1
try:
open(f, "w").write('\n'.join(new_lines))
except Exception as e:
sys.stdout.write("%s: %s\n" % (f, str(e)))
sys.stdout.write("OK, %d files modified\n" % modified)
return True
if args[0] == "binding":
if len(args) > 3:
raise Exception("Too many arguments")
if len(args) < 3:
raise Exception("Expected: mod profile")
module = args[1]
sta = convert_stations(args[2])
if not os.path.exists(os.path.join(self.env.key_dir, module, "station_" + sta)):
raise Exception("%s/%s: binding not found" % (module, args[2]))
os.remove(os.path.join(self.env.key_dir, module, "station_" + sta))
f = os.path.join(self.env.key_dir, "station_" + sta)
try:
lines = [line.strip() for line in open(f, "r").readlines()]
except OSError:
pass
new_lines = []
is_modified = False
for line in lines:
# Comment line
if line.startswith("#"):
new_lines.append(line)
continue
# Empty line
if len(line) == 0:
new_lines.append(line)
continue
toks = line.split(':')
# Wrong module name
if toks[0] != module:
new_lines.append(line)
continue
# Profile found
if len(toks) == 1:
# Filter line
is_modified = True
continue
new_lines.append(line)
if is_modified:
try:
open(f, "w").write('\n'.join(new_lines))
except Exception as e:
sys.stdout.write("%s: %s\n" % (f, str(e)))
return True
raise Exception("Invalid argument: %s" % args[0])
def commandPrint(self, args):
if len(args) == 0:
raise Exception("Missing operand")
if args[0] == "station":
if len(args) != 2:
raise Exception("missing argument, expected: sta")
sta = convert_stations(args[1])
key = os.path.join(self.env.key_dir, "station_" + sta)
try:
lines = [line.strip() for line in open(key, "r").readlines()]
except IOError as e:
raise Exception("%s: station not configured" % sta)
except Exception as e:
raise Exception("%s: unexpected error: %s" % (sta, str(e)))
first = True
for line in lines:
# Comment line
if line.startswith("#"):
continue
# Empty line
if len(line) == 0:
continue
toks = line.split(':')
if len(toks) == 1:
binding = os.path.join(
self.env.key_dir, toks[0], "station_" + sta)
else:
binding = os.path.join(
self.env.key_dir, toks[0], "profile_" + toks[1])
if not first:
sys.stdout.write("\n")
first = False
sys.stdout.write("[%s]\n" % toks[0])
sys.stdout.write("%s\n" % binding)
try:
data = open(binding).read()
sys.stdout.write("-"*80 + "\n")
sys.stdout.write(data)
sys.stdout.write("-"*80 + "\n")
except IOError as e:
sys.stdout.write("!binding not found\n")
except Exception as e:
sys.stdout.write("!unexpected error: %s\n" % str(e))
else:
raise Exception("Invalid argument: %s" % args[0])
def commandSet(self, args):
if len(args) == 0:
raise Exception("Missing operand")
if args[0] == "profile":
if len(args) != 4:
raise Exception(
"missing arguments, expected: module profile station-selector")
module = args[1]
profile = args[2]
wild = convert_wildcard(args[3])
if not os.path.exists(os.path.join(self.env.key_dir, module, "profile_" + profile)):
raise Exception("%s/%s: profile not found" % (module, profile))
modified = 0
for f in glob.glob(os.path.join(self.env.key_dir, "station_" + wild)):
lines = [line.strip() for line in open(f, "r").readlines()]
module_found = False
is_modified = False
for i in range(len(lines)): #pylint: disable=C0200
line = lines[i]
# Comment line
if line.startswith("#"):
continue
# Empty line
if len(line) == 0:
continue
toks = line.split(':')
# Wrong module name
if toks[0] != module:
continue
module_found = True
# No profile
if len(toks) == 1:
toks.append("")
# Profile already set
elif toks[1] == profile:
continue
toks[1] = profile
lines[i] = ':'.join(toks)
is_modified = True
if not module_found:
lines.append("%s:%s\n" % (module, profile))
is_modified = True
if is_modified:
modified += 1
try:
open(f, "w").write('\n'.join(lines))
except Exception as e:
sys.stdout.write("%s: %s\n" % (f, str(e)))
sys.stdout.write("OK, %d files modified\n" % modified)
return True
if args[0] == "module":
if len(args) != 3:
raise Exception(
"missing arguments, expected: module station-selector")
module = args[1]
wild = convert_wildcard(args[2])
modified = 0
for f in glob.glob(os.path.join(self.env.key_dir, "station_" + wild)):
lines = [line.strip() for line in open(f, "r").readlines()]
module_found = False
is_modified = False
for i in range(len(lines)): #pylint: disable=C0200
line = lines[i]
# Comment line
if line.startswith("#"):
continue
# Empty line
if len(line) == 0:
continue
toks = line.split(':')
# Wrong module name
if toks[0] != module:
continue
module_found = True
lines[i] = module
is_modified = True
if not module_found:
lines.append("%s\n" % module)
is_modified = True
if is_modified:
modified += 1
try:
open(f, "w").write('\n'.join(lines))
except Exception as e:
sys.stdout.write("%s: %s\n" % (f, str(e)))
sys.stdout.write("OK, %d files modified\n" % modified)
return True
raise Exception("Invalid argument: %s" % args[0])
def commandRemove(self, args):
if len(args) == 0:
raise Exception("Missing operand")
if args[0] == "profile":
if len(args) != 4:
raise Exception(
"Missing arguments, expected: module profile station-selector")
module = args[1]
profile = args[2]
wild = convert_wildcard(args[3])
modified = 0
for f in glob.glob(os.path.join(self.env.key_dir, "station_" + wild)):
lines = [line.strip() for line in open(f, "r").readlines()]
is_modified = False
for i in range(len(lines)): #pylint: disable=C0200
line = lines[i]
# Comment line
if line.startswith("#"):
continue
# Empty line
if len(line) == 0:
continue
toks = line.split(':')
# No profile
if len(toks) == 1:
continue
# Wrong module name
if toks[0] != module:
continue
# Wrong profile name
if toks[1] != profile:
continue
lines[i] = module
is_modified = True
continue
if is_modified:
modified += 1
if (len(lines) > 0) and (len(lines[-1]) > 0):
lines.append("")
try:
open(f, "w").write('\n'.join(lines))
except Exception as e:
sys.stdout.write("%s: %s\n" % (f, str(e)))
sys.stdout.write("OK, %d files modified\n" % modified)
return True
if args[0] == "module":
if len(args) != 3:
raise Exception(
"Missing arguments, expected: module station-selector")
module = args[1]
wild = convert_wildcard(args[2])
modified = 0
for f in glob.glob(os.path.join(self.env.key_dir, "station_" + wild)):
lines = [line.strip() for line in open(f, "r").readlines()]
new_lines = []
is_modified = False
for line in lines:
# Comment line
if line.startswith("#"):
new_lines.append(line)
continue
# Empty line
if len(line) == 0:
new_lines.append(line)
continue
toks = line.split(':')
# Wrong module name
if toks[0] != module:
new_lines.append(line)
continue
# Filter line
is_modified = True
if is_modified:
modified += 1
if (len(new_lines) > 0) and (len(new_lines[-1]) > 0):
new_lines.append("")
try:
open(f, "w").write('\n'.join(new_lines))
except Exception as e:
sys.stdout.write("%s: %s\n" % (f, str(e)))
try:
os.remove(os.path.join(self.env.key_dir,
module, os.path.basename(f)))
except OSError:
pass
sys.stdout.write("OK, %d files modified\n" % modified)
return True
raise Exception("Invalid argument: %s" % args[0])

View File

@ -0,0 +1,260 @@
import os, sys, tempfile
import datetime, time, re
from seiscomp import mseedlite as mseed
def _timeparse(t, format):
"""Parse a time string that might contain fractions of a second.
Fractional seconds are supported using a fragile, miserable hack.
Given a time string like '02:03:04.234234' and a format string of
'%H:%M:%S', time.strptime() will raise a ValueError with this
message: 'unconverted data remains: .234234'. If %S is in the
format string and the ValueError matches as above, a datetime
object will be created from the part that matches and the
microseconds in the time string.
"""
try:
return datetime.datetime(*time.strptime(t, format)[0:6]).time()
except ValueError as msg:
if "%S" in format:
msg = str(msg)
mat = re.match(r"unconverted data remains:"
" \.([0-9]{1,6})$", msg)
if mat is not None:
# fractional seconds are present - this is the style
# used by datetime's isoformat() method
frac = "." + mat.group(1)
t = t[:-len(frac)]
t = datetime.datetime(*time.strptime(t, format)[0:6])
microsecond = int(float(frac)*1e6)
return t.replace(microsecond=microsecond)
else:
mat = re.match(r"unconverted data remains:"
" \,([0-9]{3,3})$", msg)
if mat is not None:
# fractional seconds are present - this is the style
# used by the logging module
frac = "." + mat.group(1)
t = t[:-len(frac)]
t = datetime.datetime(*time.strptime(t, format)[0:6])
microsecond = int(float(frac)*1e6)
return t.replace(microsecond=microsecond)
raise
def timeparse(t):
return _timeparse(t, "%Y/%m/%d %H:%M:%S")
class Input(mseed.Input):
def __init__(self, server, streams,
stime=None, etime=None, timeout=None, verbose=0):
# XXX Add the possibility for supplying stime and etime as
# individual times for each stream.
"""
'streams' must be a list containing tuples of (net,sta,loc,cha)
"""
import subprocess
streams = [ "%-3s %5s %s%3s.D" % s for s in streams ]
streams.sort()
self.tmp = tempfile.NamedTemporaryFile(mode="w", prefix="slinktool.")
self.tmp.write("\n".join(streams)+"\n")
self.tmp.flush()
if verbose:
sys.stderr.write("\n".join(streams)+"\n")
slinktool = os.getenv("SLINKTOOL")
if not slinktool:
slinktool = "slinktool"
args = [slinktool, "-l", self.tmp.name, "-o", "-"]
if stime:
args.append("-tw")
tw = "%d,%d,%d,%d,%d,%d:" % (stime.year,stime.month,stime.day,stime.hour,stime.minute,stime.second)
if etime:
rw += "%d,%d,%d,%d,%d,%d" % (etime.year,etime.month,etime.day,etime.hour,etime.minute,etime.second)
args.append(tw)
if verbose: args.append("-v")
if timeout:
try: assert int(timeout) > 0
except: raise TypeError("illegal timeout parameter")
args += ["-nt", "%d" % int(timeout)]
args.append(server)
# start 'slinktool' as sub-process
self.popen = subprocess.Popen(args, stdout=subprocess.PIPE, shell=False)
infile = self.popen.stdout
mseed.Input.__init__(self, infile)
def __del__(self):
"""
Shut down SeedLink connections and close input.
"""
sys.stderr.write("shutting down slinktool\n")
sys.stderr.flush()
slinktool_pid = self.popen.pid
# It would of course be much better to send SIGTERM,
# but somehow slinktool often appears to ignore it.
# XXX Need to figure out why, and perhaps fix it (not critical).
self.popen.kill()
self.popen.communicate()
# mseed.Input.__del__(self) # closes the input file
class Input2(mseed.Input):
def __init__(self, server, streams, stime=None, etime=None, verbose=0):
"""
XXX information not uptodate!!! XXX
'streams' must be a dict containing tuples of (stime, etime),
with the key being the stream_id and stime and etime being
the starting and end time of the time window, respectively.
The times must be seis.Time objects. For instance
stime = seis.Time(...)
etime = seis.Time(...)
streams["GE.KBS.00.BHZ.D"] = (stime, etime)
It is more efficient to request the same time interval for
all streams. Wildcards for the channels are allowed. If
stime is None, only new data are retrieved as they come in.
"""
streams = [ "%-3s %5s %s%3s.D" % tuple(s.split(".")[:4])
for s in streams ]
streams.sort()
self.tmp = tempfile.NamedTemporaryFile(mode="w", prefix="slinktool.")
self.tmp.write("\n".join(streams)+"\n")
sys.stderr.write("\n".join(streams)+"\n")
self.tmp.flush()
cmd = "slinktool -l %s -o -" % self.tmp.name
if stime:
assert isinstance(stime, seis.Time)
cmd += " -tw %d,%d,%d,%d,%d,%d:" % stime.asDate
if etime:
assert isinstance(etime, seis.Time)
cmd += "%d,%d,%d,%d,%d,%d" % etime.asDate
cmd = cmd + "%s '%s'" % (verbose*" -v", server)
infile = os.popen(cmd)
mseed.Input.__init__(self, infile)
def available(server="localhost:18000",
time_window=None, stream_ids=None, verbose=0):
"""
Connects to server and returns a dictionary of lists of available
time windows as tuples of (start_time, end_time) for each available
stream. The stream set can be limited by specifying a list of
stream_ids in the format usual format, i.e. net.sta.loc.cha.type,
e.g. "GE.KBS.00.BHZ.D".
Note that often the returned lists contain only one time tuple,
corresponding to one contiguous time window available.
NEW:
The search for available data can be limited to a time window by
specifying the "time_window" parameter, which must be a tuple
containing the starting and end time as seis.Time objects.
"""
import re
if time_window:
stime, etime = time_window
assert stime <= etime
else:
stime, etime = None, None
cmd = "slinktool -Q %s %s " % (verbose*"-v ", server)
infile = os.popen(cmd)
windows = {}
# parse the output of "slinktool -Q"
# It is assumed that the lines consist of the fields
# net,sta,[loc,], cha, type, date1, time1, "-", date2, time2
# Since the location code (loc) may or may not be present, we
# determine the position of the dash "-" to determine where the
# other fields are.
regex = re.compile("^[A-Z][A-Z]\ [A-Z].*[12][0-9]{3}(/[0-9]{2}){2}.*$")
for line in infile:
if regex.match(line): # line containing a time window, a bit crude
line = line.split()
try:
dash = line.index("-")
except ValueError:
continue
if dash==7: # location code is present
loc = line[2]
else: loc = ""
net, sta, cha, typ = line[0], line[1], line[dash-4], line[dash-3]
stream_id = "%s.%s.%s.%s.%s" % (net, sta, loc, cha, typ)
if stream_ids and stream_id not in stream_ids:
continue
t1 = seis.Time("%s %s" % (line[dash-2], line[dash-1]))
t2 = seis.Time("%s %s" % (line[dash+1], line[dash+2]))
if stime and t2<stime or etime and t1>etime:
continue # non-overlapping time windows
if stime and t1<stime:
t1 = stime
if etime and t2>etime:
t2 = etime
if not stream_id in windows:
windows[stream_id] = []
windows[stream_id].append((t1,t2))
elif verbose:
# probably some diagnostic output
sys.stdout.write("%s\n" % line.strip())
return windows
def server_version(host, port=18000):
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((host, port))
except:
return None
s.send("HELLO\n")
data = s.recv(1024)
s.close()
if data[:8] != "SeedLink":
return None
return data[10:13]
def server_running(host, port=18000):
if server_version(host, port):
return True
return False

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More