@@ -75,6 +75,136 @@ def prefer_high_sampling_rate(waveform_dir, logger=None):
7575 file .unlink ()
7676
7777
78+ def get_fetchdata_layout (config ):
79+ """
80+ Get the fetchdata output layout.
81+
82+ Supported values:
83+ - ``event_dirs``: legacy layout with per-event waveform/station folders
84+ - ``event_files``: bundled per-event files (event.mseed/stations.xml)
85+
86+ :param config: config object
87+ :type config: dict
88+ :return: normalized layout name
89+ :rtype: str
90+ """
91+ layout = str (config .get ('fetchdata_layout' , 'event_dirs' )).strip ().lower ()
92+ if layout in ('legacy' , 'event_dirs' , 'event_dir' ):
93+ return 'event_dirs'
94+ if layout in ('event_files' , 'bundled' ):
95+ return 'event_files'
96+ return 'event_dirs'
97+
98+
99+ def get_event_xml_file (evid_dir , evid ):
100+ """
101+ Return the event QuakeML file path for an event directory.
102+
103+ Tries ``{evid}.xml`` first, then ``event.xml``.
104+
105+ :param evid_dir: event directory
106+ :type evid_dir: pathlib.Path
107+ :param evid: event ID
108+ :type evid: str
109+ :return: event xml file path or None
110+ :rtype: pathlib.Path or None
111+ """
112+ evid_dir = pathlib .Path (evid_dir )
113+ candidates = (
114+ evid_dir / f'{ evid } .xml' ,
115+ evid_dir / 'event.xml' ,
116+ )
117+ for xml_file in candidates :
118+ if xml_file .exists ():
119+ return xml_file
120+ return None
121+
122+
123+ def get_event_layout_paths (config , evid ):
124+ """
125+ Build event-specific paths for the configured fetchdata layout.
126+
127+ :param config: config object
128+ :type config: dict
129+ :param evid: event ID
130+ :type evid: str
131+ :return: dictionary with layout and file/directory paths
132+ :rtype: dict
133+ """
134+ event_dir = pathlib .Path (config ['event_dir' ])
135+ evid_dir = event_dir / f'{ evid } '
136+ layout = get_fetchdata_layout (config )
137+ if layout == 'event_files' :
138+ return {
139+ 'layout' : layout ,
140+ 'evid_dir' : evid_dir ,
141+ 'waveform_dir' : evid_dir / '.waveforms' ,
142+ 'station_dir' : evid_dir / '.stations' ,
143+ 'event_xml_file' : evid_dir / 'event.xml' ,
144+ 'waveform_file' : evid_dir / 'event.mseed' ,
145+ 'station_file' : evid_dir / 'stations.xml' ,
146+ }
147+ return {
148+ 'layout' : layout ,
149+ 'evid_dir' : evid_dir ,
150+ 'waveform_dir' : evid_dir / config ['waveform_dir' ],
151+ 'station_dir' : evid_dir / config ['station_dir' ],
152+ 'event_xml_file' : evid_dir / f'{ evid } .xml' ,
153+ 'waveform_file' : None ,
154+ 'station_file' : None ,
155+ }
156+
157+
158+ def bundle_waveforms_to_mseed (waveform_dir , outfile ):
159+ """
160+ Bundle all waveform miniSEED files from a directory into one file.
161+
162+ :param waveform_dir: source waveform directory
163+ :type waveform_dir: pathlib.Path
164+ :param outfile: output miniSEED file
165+ :type outfile: pathlib.Path
166+ :return: True if output file was written, False otherwise
167+ :rtype: bool
168+ """
169+ from obspy import Stream , read
170+ waveform_dir = pathlib .Path (waveform_dir )
171+ outfile = pathlib .Path (outfile )
172+ stream = Stream ()
173+ for mseed_file in sorted (waveform_dir .glob ('*.mseed' )):
174+ stream += read (str (mseed_file ))
175+ if len (stream ) == 0 :
176+ return False
177+ stream .write (str (outfile ), format = 'MSEED' )
178+ return True
179+
180+
181+ def bundle_stations_to_xml (station_dir , outfile ):
182+ """
183+ Bundle all station XML files from a directory into one StationXML file.
184+
185+ :param station_dir: source station XML directory
186+ :type station_dir: pathlib.Path
187+ :param outfile: output StationXML file
188+ :type outfile: pathlib.Path
189+ :return: True if output file was written, False otherwise
190+ :rtype: bool
191+ """
192+ from obspy import read_inventory
193+ station_dir = pathlib .Path (station_dir )
194+ outfile = pathlib .Path (outfile )
195+ inv = None
196+ for station_file in sorted (station_dir .glob ('*.xml' )):
197+ _inv = read_inventory (str (station_file ))
198+ if inv is None :
199+ inv = _inv
200+ else :
201+ inv += _inv
202+ if inv is None :
203+ return False
204+ inv .write (str (outfile ), format = 'STATIONXML' )
205+ return True
206+
207+
78208def check_station (station , station_codes ):
79209 """
80210 Check if a station matches the specified station codes.
@@ -116,8 +246,8 @@ def get_picked_station_codes(evid_dir, evid):
116246 """
117247 import warnings
118248 from obspy import read_events
119- xml_file = pathlib . Path (evid_dir ) / f' { evid } .xml'
120- if not xml_file . exists () :
249+ xml_file = get_event_xml_file (evid_dir , evid )
250+ if xml_file is None :
121251 return None
122252 with warnings .catch_warnings ():
123253 warnings .simplefilter ('ignore' )
0 commit comments