Coverage for /opt/obspy/update-docs/src/obspy/obspy/xseed/parser : 82%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# -*- coding: utf-8 -*- Main module containing XML-SEED parser.
:copyright: The ObsPy Development Team (devs@obspy.org) :license: GNU Lesser General Public License, Version 3 (http://www.gnu.org/copyleft/lesser.html) """
# @see: http://www.iris.edu/manuals/SEEDManual_V2.4.pdf, p. 22-24 'V': {'name': 'Volume Index Control Header', 'blockettes': [10, 11, 12]}, 'A': {'name': 'Abbreviation Dictionary Control Header', 'blockettes': [30, 31, 32, 33, 34, 41, 43, 44, 45, 46, 47, 48]}, 'S': {'name': 'Station Control Header', 'blockettes': [50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62]} }
# Index fields of the abbreviation blockettes. 31: 'comment_code_key', 32: 'source_lookup_code', 33: 'abbreviation_lookup_code', 34: 'unit_lookup_code', 35: 'beam_lookup_code' }
""" The XML-SEED parser class parses dataless or full SEED volumes.
.. seealso::
The SEED file format description can be found at http://www.iris.edu/manuals/SEEDManual_V2.4.pdf.
The XML-SEED format was proposed in:
* http://www.orfeus-eu.org/Organization/Newsletter/vol6no2/xml.shtml * http://www.jamstec.go.jp/pacific21/xmlninja/. """
compact=False): """ Initializes the SEED parser.
:param data: Filename, URL, XSEED/SEED string, file pointer or StringIO :type debug: Boolean. :param debug: Enables a verbose debug log during parsing of SEED file. :type strict: Boolean. :param strict: Parser will raise an exception if SEED files does not stay within the SEED specifications. :type compact: Boolean. :param compact: SEED volume will contain compact data strings. Missing time strings will be filled with 00:00:00.0000 if this option is disabled. """ # All parsed data is organized in volume, abbreviations and a list of # stations. # if a file name is given, read it directly to the parser object
""" """ return 'No data' except: return 'No data' # Sort alphabetically. network["network_name"]) station["station_name"]) channel["start_date"] else "" channel["end_date"] else "" (channel["channel_id"], channel["sampling_rate"], channel["instrument"], start_date, end_date)
""" General parser method for XML-SEED and Dataless SEED files.
:type data: Filename, URL, Basestring or StringIO object. :param data: Filename, URL or XSEED/SEED string as file pointer or StringIO. """ # try to transform everything into StringIO object # if it starts with /path/to/ try to search in examples except: # otherwise just try to read the given /path/to folder pass # some URL data = urllib2.urlopen(data).read() # looks like a file - read it # but could also be a big string with data raise TypeError # check first byte of data StringIO object # SEED volumes starts with a number # XML files should always starts with an '<' else:
""" Returns a XSEED representation of the current Parser object.
:type version: float, optional :param version: XSEED version string (default is ``1.1``). :type split_stations: boolean, optional :param split_stations: Splits stations containing multiple channels into multiple documents. :rtype: str or dict :return: Returns either a string or a dict of strings depending on the flag ``split_stations``. """ raise SEEDParserException("Unknown XML-SEED version!") # Nothing to write if not all necessary data is available. len(self.stations) == 0: msg = 'No data to be written available.' raise SEEDParserException(msg) # Check blockettes: msg = 'Not all necessary blockettes are available.' raise SEEDParserException(msg) # Add blockettes 11 and 12 only for XSEED version 1.0. # Now start actually filling the XML tree. # Volume header: # Delete blockettes 11 and 12 if necessary. # Abbreviations: utils.toTag('Abbreviation Dictionary Control Header')) # Don't split stations # To pass the XSD schema test an empty time span control header # is added to the end of the file. # Also no data is present in all supported SEED files. # Return single XML String. encoding='UTF-8') else: # generate a dict of XML resources for each station # To pass the XSD schema test an empty time span control # header is added to the end of the file. SubElement(doc, utils.toTag('Timespan Control Header')) # Also no data is present in all supported SEED files. SubElement(doc, utils.toTag('Data Records')) xml_declaration=True, encoding='UTF-8')
""" Writes a XML-SEED file with given name. """ # past meta data - append timestamp else: # current meta data - leave original filename else: raise TypeError
""" Returns a SEED representation of the current Parser object. """ # Nothing to write if not all necessary data is available. msg = 'No data to be written available.' raise SEEDParserException(msg) # Check blockettes: msg = 'Not all necessary blockettes are available.' raise SEEDParserException(msg) # String to be written to: # Delete Blockette 11 again. # Finally write the actual SEED String. # Remove name of the stations.
""" Writes a dataless SEED file with given name. """ fh = open(filename, 'wb') fh.write(self.getSEED(*args, **kwargs)) fh.close()
""" Returns a RESP representation of the current Parser object.
It aims to produce the same RESP files as when running rdseed with the command: "rdseed -f seed.test -R". """ # Check if there are any stations at all. raise Exception('No data to be written.') # Channel Response list. # Loop over all stations. # Read the current station information and store it. # Loop over all blockettes in that station. # Catch all blockette 52. # Take old list and send it to the RESP parser. # Send the blockettes to the parser and append to list. # Create the filename. % (cur_network, cur_station, cur_location, cur_channel) # Create new StringIO and list. # Write header and the first two lines to the string. '#\t\t<< obspy.xseed, Version 0.1.3 >>\n' + \ '#\t\t\n' + \ '#\t\t======== CHANNEL RESPONSE DATA ========\n' + \ 'B050F03 Station: %s\n' % cur_station + \ 'B050F16 Network: %s\n' % cur_network # Write to StringIO. # It might happen that no blockette 52 is specified, # One last time for the last channel. # Combine multiple channels. else: for _i in xrange(1, len(channel_list)): channel_list[_i][1].seek(0, 0) channel_list[0][1].write(channel_list[_i][1].read()) new_resp_list.append(channel_list[0])
""" Selects all blockettes related to given SEED id and datetime. """ # parse blockettes if not SEED. Needed foe XSEED to be intialized. # XXX: Should potentially be fixed at some point. # split id else: # create a copy of station list # filter blockettes list by given SEED id continue continue blk.end_effective_date < datetime: # check number of selected channels (equals number of blockette 52)
""" Return PAZ.
.. note:: Currently only the Laplace transform is supported, that is blockettes 43 and 53. A UserWarning will be raised for unsupported response blockettes, however all other values, such as overall sensitivity, normalization constant, etc. will be still returned if found.
:type seed_id: str :param seed_id: SEED or channel id, e.g. ``"BW.RJOB..EHZ"`` or ``"EHE"``. :type datetime: :class:`~obspy.core.utcdatetime.UTCDateTime`, optional :param datetime: Timestamp of requested PAZ values :return: Dictionary containing PAZ as well as the overall sensitivity, the gain in the dictionary is the A0 normalization constant """ [blk.sensitivity_gain for blk in self.abbreviations if hasattr(blk, 'response_lookup_key') and \ blk.response_lookup_key == abbreviation][0] if hasattr(blk, 'response_lookup_key') and \ blk.response_lookup_key == abbreviation][0] else: # Check if Laplace transform 'type. Skipping other response information.' # A0_normalization_factor # Poles # Zeros
""" Return Coordinates (from blockette 52)
:type seed_id: str :param seed_id: SEED or channel id, e.g. ``"BW.RJOB..EHZ"`` or ``"EHE"``. :type datetime: :class:`~obspy.core.utcdatetime.UTCDateTime`, optional :param datetime: Timestamp of requested PAZ values :return: Dictionary containing Coordinates (latitude, longitude, elevation) """
""" Writes for each channel a RESP file within a given folder.
:param folder: Folder name. :param zipped: Compresses all files into a single ZIP archive named by the folder name extended with the extension '.zip'. """ new_resp_list = self.getRESP() # Check if channel information could be found. if len(new_resp_list) == 0: msg = ("No channel information could be found. The SEED file " "needs to contain information about at least one channel.") raise Exception(msg) if not zipped: # Write single files. for response in new_resp_list: if folder: file = open(os.path.join(folder, response[0]), 'w') else: file = open(response[0], 'w') response[1].seek(0, 0) file.write(response[1].read()) file.close() else: # Create a ZIP archive. zip_file = zipfile.ZipFile(folder + os.extsep + "zip", "w") for response in new_resp_list: response[1].seek(0, 0) zip_file.writestr(response[0], response[1].read()) zip_file.close()
""" Parses through a whole SEED volume.
It will always parse the whole file and skip any time span data.
:type data: File pointer or StringIO object. """ # Jump to the beginning of the file. # Retrieve some basic data like version and record length. # Check whether it starts with record sequence number 1 and a volume # index control header. # The first blockette has to be Blockette 10. # Skip the next four bytes containing the length of the blockette. # Set the version. # Get the record length. # Test record length. msg = "Got an invalid logical record length %d" % length raise SEEDParserException(msg) if self.debug: print("RECORD LENGTH: %d" % (self.record_length)) # Set all temporary attributes. # Jump back to beginning. # Read the first record. # Loop through file and pass merged records to _parseMergedData. # continued record else: # first or new type of record # only parse headers, no data if self.debug: if not record_continuation: print("========") print(record[0:8]) # Use parse once again. # Update the internal structure to finish parsing.
""" Function returning a dictionary about whats actually in the Parser object. """ new_id = "" for _i in network_id: if _i.isdigit(): new_id += _i network_id = int(new_id) "network_name": network_name}) (current_network, current_station), "station_name": blkt.site_name}) raise Exception("Something went wrong") current_station, location, channel) self._get_abbreviation(blkt.instrument_identifier)
""" Helper function returning the abbreviation for the given identifier code. """ return ""
""" Parse a XML-SEED string.
:type data: File pointer or StringIO object. """ # Set all temporary attributes. # Parse volume which is assumed to be the first header. Only parse # blockette 10 and discard the rest. self._parseXMLBlockette(headers[0].getchildren()[0], 'V', xseed_version)) # Append all abbreviations. self._parseXMLBlockette(blockette, 'A', xseed_version)) # Append all stations. self._parseXMLBlockette(blockette, 'S', xseed_version)) # Update internal values.
""" Takes a file like object and a list of blockettes containing all blockettes for one channel and writes them RESP like to the StringIO. """ # The first blockette in the list always has to be Blockette 52. 'Channel': blkt52.channel_identifier, 'Start date': blkt52.start_date, 'End date': blkt52.end_date} # Set location and end date default values or convert end time.. else: # Convert starttime. # Write Blockette 52 stuff. 'B052F03 Location: %s\n' % channel_info['Location'] + \ 'B052F04 Channel: %s\n' % channel_info['Channel'] + \ 'B052F22 Start date: %s\n' % channel_info['Start date'] + \ 'B052F23 End date: %s\n' % channel_info['End date'] + \ '#\t\t=======================================\n') # Write all other blockettes. Currently now sorting takes place. This # is just an experiment to see how rdseed does it. The Blockettes # might need to be sorted. continue self.abbreviations)) except AttributeError: msg = 'RESP output for blockette %s not implemented yet.' raise AttributeError(msg % blockette.id)
""" Takes the lxml tree of any blockette and returns a blockette object. """ # Get blockette number. raise SEEDParserException('Blockette %d not implemented!' % blockette_id) strict=self.strict, compact=self.compact, version=self.version, record_type=record_type, xseed_version=xseed_version) elif blockette_id != 0: msg = "Unknown blockette type %d found" % blockette_id raise SEEDParserException(msg)
""" Takes all blockettes of a record and return a list of finished records.
If necessary it will cut the record and return two or more flushed records.
The returned records also include the control header type code and the record continuation code. Therefore the returned record will have the length self.record_length - 6. Other methods are responsible for writing the sequence number.
It will always return a list with records. """ # Loop over all blockettes. # Never split a blockette’s “length/blockette type” section across # records. # Flush the rest of the record if necessary. # Calculate how much of the blockette is too long. # If negative overhead: Write blockette. # Otherwise finish the record and start one or more new ones. else: # The record so far not written. overhead):] # Loop over the number of records to be written. float(length)))): # It doesn't hurt to index a string more than its length. rest_of_the_record[_i * length: (_i + 1) * length] # Flush last record (length - len(return_records[-1])) # Add control header and continuation code.
""" Checks if all blockettes necessary for creating a SEED String are available. """ return False not 34 in abb_blockettes: return False # Check every station: not 58 in stat_blockettes: return False
""" Compares two blockettes. """ # Continue if just some meta data.
""" Takes everything in the self.temp dictionary and writes it into the volume, abbreviations and stations attributes of the class.
The self.temp dictionary can only contain one seed volume with a correct structure.
This method will try to merge everything, discard double entries and adjust abbreviations.
It will also discard unnecessary blockettes that will be created again when writing SEED or XSEED. """ # If called without a filled temporary dictionary do nothing. return # Check if everything is empty. len(self.stations) == 0: # Delete Blockette 11 and 12. if i.id not in [11, 12]] else: msg = 'Merging is an experimental feature and still contains ' + \ 'a lot of errors!' warnings.warn(msg, UserWarning) # XXX: Sanity check for multiple Blockettes. Remove duplicates. # self._removeDuplicateAbbreviations() # Check the abbreviations. for blkt in self.temp['abbreviations']: id = blkt.blockette_type # Loop over all existing abbreviations and find those with the # same id and content. cur_index = 1 # Helper variable. blkt_done = False for ex_blkt in self.abbreviations: if id != ex_blkt.blockette_type: continue # Raise the current index if it is the same blockette. cur_index += 1 if not self._compareBlockettes(blkt, ex_blkt): continue # Update the current blockette and all abbreviations. self._updateTemporaryStations(id, getattr(ex_blkt, INDEX_FIELDS[id])) blkt_done = True break if not blkt_done: self._updateTemporaryStations(id, cur_index) # Append abbreviation. setattr(blkt, INDEX_FIELDS[id], cur_index) self.abbreviations.append(blkt) # Update the stations. self.stations.extend(self.temp['stations']) #XXX Update volume control header!
# Also make the version of the format 2.4.
""" Loops over all stations, finds the corresponding blockettes and changes all abbreviation lookup codes. """ # Blockette dictionary which maps abbreviation IDs and and fields. index = { # Abbreviation Blockette : {Station Blockette: (Fields)} 30: {52: (16,)}, 31: {51: (5,), 59: (5,)}, 33: {50: (10,), 52: (6,)}, 34: {52: (8, 9), 53: (5, 6), 54: (5, 6), 55: (4, 5)} } blockettes = index[blkt_id] # Loop over all stations. stations = self.temp['stations'] for station in stations: for blkt in station: try: fields = blockettes[blkt.blockette_type] except: continue for field in fields: setattr(blkt, blkt.getFields()[field - 2].field_name, index_nr)
""" This method takes any merged SEED record and writes its blockettes in the corresponding dictionary entry of self.temp. """ # Create StringIO for easier access. # Do not do anything if no data is passed or if a time series header # is passed. return # Set standard values. # Find out what kind of record is being parsed. # Create new station blockettes list. # Just one Volume header per file allowed. msg = 'More than one Volume index control header found!' raise SEEDParserException(msg) else: # Just one abbreviations header allowed! 'Headers found!' # Loop over all blockettes in data. # remove spaces between blockettes raise SEEDParserException('Blockette %d not implemented!' % blockette_id) strict=self.strict, compact=self.compact, version=self.version, record_type=record_type) []).append(blockette_obj) elif blockette_id != 0: msg = "Unknown blockette type %d found" % blockette_id raise SEEDParserException(msg) # check if everything is parsed warnings.warn("There exist unparsed elements!")
""" Creates blockettes 11 and 12 for SEED writing and XSEED version 1.1 writing. """ # All the following unfortunately is necessary to get a correct # Blockette 11: # Start with the station strings to be able to write Blockette 11 # later on. The created list will contain lists with the first item # being the corresponding station identifier code and each part of the # record being a separate item. # Loop over all stations. # Blockette 50 always should be the first blockette # Loop over blockettes. # Make abbreviations. [_i[0].station_call_letters for _i in self.stations] # Blockette 12 is also needed. cur_count += len(volume) - 1 self._deleteBlockettes11and12() continue
""" Deletes blockette 11 and 12. """ |