1 | #! /usr/bin/env python |
---|
2 | # -*- coding: UTF8 -*- |
---|
3 | |
---|
4 | """ |
---|
5 | use lxml parsing to reformat seiscomp3's xml dump into evt format for seismic handler |
---|
6 | maybe there the possibility to use xslt |
---|
7 | """ |
---|
8 | |
---|
9 | __licence__ = "GPLv3" |
---|
10 | __author__ = "Marcus Walther, walther@szgrf.bgr.de" |
---|
11 | __url__ = "http://www.seismic-handler.org/portal/wiki/ShSoftScpToEvt" |
---|
12 | __version__ = 0.1 |
---|
13 | __revision__ = __id__ = "$Id$" |
---|
14 | |
---|
15 | import sys |
---|
16 | from time import strftime, strptime |
---|
17 | |
---|
18 | try: |
---|
19 | from lxml import etree |
---|
20 | except ImportError: |
---|
21 | print "fatal error: lxml module not found!" |
---|
22 | sys.exit(1) |
---|
23 | |
---|
24 | # list of stations to be put in evt file |
---|
25 | # note: stations = [] will allow all |
---|
26 | stations = [ |
---|
27 | "WET", "TNS", |
---|
28 | ] |
---|
29 | def stationFilter(data): |
---|
30 | if not len(stations): |
---|
31 | return True |
---|
32 | |
---|
33 | if data["station"] in stations: |
---|
34 | return True |
---|
35 | else: |
---|
36 | return False |
---|
37 | |
---|
38 | class sc2evt(object): |
---|
39 | """convert seiscomp3 xmldump to evt file""" |
---|
40 | |
---|
41 | def __init__(self, src, trgt): |
---|
42 | self.target = trgt |
---|
43 | |
---|
44 | # older version don't support XMLParser |
---|
45 | try: |
---|
46 | parser = etree.XMLParser(remove_blank_text=True) |
---|
47 | except AttributeError: |
---|
48 | parser = None |
---|
49 | |
---|
50 | try: |
---|
51 | x = etree.parse(src, parser) |
---|
52 | except Exception, e: |
---|
53 | print e |
---|
54 | sys.exit(3) |
---|
55 | |
---|
56 | self.xml = x |
---|
57 | self._output() |
---|
58 | |
---|
59 | def _output(self): |
---|
60 | if "arrivals" not in self.__dict__: |
---|
61 | self._read() |
---|
62 | |
---|
63 | # width of first column |
---|
64 | WIDTH = 23 |
---|
65 | FINAL = "--- End of Phase ---\n\n" |
---|
66 | |
---|
67 | fields_std = { |
---|
68 | "station": "Station code", |
---|
69 | "time": "Onset time", |
---|
70 | "phase": "Phase name", |
---|
71 | "channel": "Component", |
---|
72 | "distance": "Distance (deg)", |
---|
73 | "azimuth": "Epi-Azimuth (deg)", |
---|
74 | } |
---|
75 | |
---|
76 | fields_event = { |
---|
77 | "latitude": "Latitude", |
---|
78 | "longitude": "Longitude", |
---|
79 | "depth": "Depth (km)", |
---|
80 | "time": "Origin time" |
---|
81 | } |
---|
82 | |
---|
83 | fields_mag = { |
---|
84 | "Mw(mB)": "Broadband Magnitude", |
---|
85 | } |
---|
86 | |
---|
87 | fields_info = { |
---|
88 | "Pick Type": "automatic", |
---|
89 | } |
---|
90 | |
---|
91 | formatted = {} |
---|
92 | for a in filter(stationFilter, self.arrivals): |
---|
93 | for field in a: |
---|
94 | try: |
---|
95 | formfield = fields_std[field] |
---|
96 | except KeyError: |
---|
97 | continue |
---|
98 | |
---|
99 | if field == "channel": |
---|
100 | value = a[field][-1] |
---|
101 | else: |
---|
102 | value = a[field] |
---|
103 | |
---|
104 | try: |
---|
105 | formatted[a["station"]][formfield] = value |
---|
106 | except KeyError: |
---|
107 | formatted[a["station"]] = {formfield: value} |
---|
108 | |
---|
109 | # add info field(s) |
---|
110 | formatted[a["station"]].update(fields_info) |
---|
111 | |
---|
112 | # add event info to last pick |
---|
113 | last = formatted[a["station"]] |
---|
114 | for e in self.event: |
---|
115 | try: |
---|
116 | formfield = fields_event[e] |
---|
117 | except KeyError: |
---|
118 | continue |
---|
119 | |
---|
120 | last[formfield] = self.event[e] |
---|
121 | |
---|
122 | # add magnitude information |
---|
123 | try: |
---|
124 | last[fields_mag[self.event["magnitude_type"]]] = self.event["magnitude"] |
---|
125 | except: |
---|
126 | pass |
---|
127 | |
---|
128 | for entry in formatted.values(): |
---|
129 | for line in entry: |
---|
130 | print >> self.target, "%s%s: %s" % (line, " "*(WIDTH-len(line)), entry[line]) |
---|
131 | print >> self.target, FINAL |
---|
132 | |
---|
133 | def _read(self): |
---|
134 | x = self.xml |
---|
135 | |
---|
136 | # get information by xpath usage |
---|
137 | picks_raw = x.xpath("/seiscomp/EventParameters/pick") |
---|
138 | arrivals_raw = x.xpath("/seiscomp/EventParameters/origin/arrival") |
---|
139 | |
---|
140 | # no picks found |
---|
141 | if not len(picks_raw) or not len(arrivals_raw): |
---|
142 | print "error: no pick or arrival information found" |
---|
143 | print "error: maybe you have a very old version of libxml/libxslt" |
---|
144 | sys.exit(4) |
---|
145 | |
---|
146 | # event information |
---|
147 | self.event = { |
---|
148 | "time": self.sctime2sh(x.xpath("/seiscomp/EventParameters/origin/time/value")[0].text), |
---|
149 | "latitude": x.xpath("/seiscomp/EventParameters/origin/latitude/value")[0].text, |
---|
150 | "longitude": x.xpath("/seiscomp/EventParameters/origin/longitude/value")[0].text, |
---|
151 | "depth": x.xpath("/seiscomp/EventParameters/origin/depth/value")[0].text, |
---|
152 | "magnitude": x.xpath("/seiscomp/EventParameters/origin/networkMagnitude/magnitude/value")[0].text, |
---|
153 | "magnitude_type": x.xpath("/seiscomp/EventParameters/origin/networkMagnitude/type")[0].text |
---|
154 | } |
---|
155 | |
---|
156 | # build hash of picks |
---|
157 | picks = {} |
---|
158 | for pick in picks_raw: |
---|
159 | wf = pick.xpath("./waveformID")[0] |
---|
160 | picks[pick.get("publicID")] = { |
---|
161 | "network": wf.get("networkCode"), |
---|
162 | "station": wf.get("stationCode"), |
---|
163 | "channel": wf.get("channelCode"), |
---|
164 | "time": self.sctime2sh(pick.xpath("./time/value")[0].text) |
---|
165 | } |
---|
166 | |
---|
167 | arrivals = [] |
---|
168 | # connect arrivals and picks |
---|
169 | for arrival in arrivals_raw: |
---|
170 | # skip bad arrivals |
---|
171 | if arrival.get("weight") == "0": |
---|
172 | continue |
---|
173 | |
---|
174 | try: |
---|
175 | pinfo = picks[arrival.xpath("./pickID")[0].text] |
---|
176 | except KeyError: |
---|
177 | continue |
---|
178 | |
---|
179 | pinfo["phase"] = arrival.xpath("./phase")[0].text |
---|
180 | pinfo["distance"] = arrival.xpath("./distance")[0].text |
---|
181 | pinfo["azimuth"] = arrival.xpath("./azimuth")[0].text |
---|
182 | |
---|
183 | arrivals.append(pinfo) |
---|
184 | |
---|
185 | self.arrivals = arrivals |
---|
186 | |
---|
187 | def sctime2sh(self, dtstr): |
---|
188 | """ |
---|
189 | reformat seiscomp time string into seismic handler format |
---|
190 | |
---|
191 | input format: 2008-06-14T00:03:49.76134Z |
---|
192 | output format: 14-JUN-2008_00:03:49.761 |
---|
193 | """ |
---|
194 | |
---|
195 | # we need to reformat date part only |
---|
196 | dt = strftime("%d-%b-%Y", strptime(dtstr[:10], "%Y-%m-%d")).upper() |
---|
197 | |
---|
198 | # shorten milliseconds part if necessary |
---|
199 | tt = dtstr[11:-1] |
---|
200 | if tt.index(".") < len(tt)-4: |
---|
201 | tt = tt[:tt.index(".")+4] |
---|
202 | |
---|
203 | return "_".join((dt, tt)) |
---|
204 | |
---|
205 | if __name__ == "__main__": |
---|
206 | try: |
---|
207 | i = sys.argv[1] |
---|
208 | except IndexError: |
---|
209 | print "error: please give source of seiscomp3 xmldump as command argument" |
---|
210 | sys.exit(2) |
---|
211 | |
---|
212 | try: |
---|
213 | o = open(sys.argv[2], "w") |
---|
214 | except IndexError: |
---|
215 | o = sys.stdout |
---|
216 | |
---|
217 | a = sc2evt(i, o) |
---|