Update to version 3.2

This commit is contained in:
2026-03-18 14:56:42 +01:00
parent f593487c77
commit 44609d367f
49 changed files with 12657 additions and 3668 deletions

View File

@@ -13,16 +13,24 @@
***************************************************************************/
#include <gempa/caps/endianess.h>
#include <gempa/caps/mseedpacket.h>
#include <gempa/caps/log.h>
#include <gempa/caps/utils.h>
#include <cmath>
#include <climits>
#include <cstdio>
#include <numeric>
#include <streambuf>
#include <iostream>
#include <libmseed.h>
namespace {
// Force local include
#include "./3rd-party/libmseed/libmseed.h"
#include "./3rd-party/libmseed/mseedformat.h"
#define MS_ISVALIDYEARDAY(Y,D) (Y >= 1900 && Y <= 2100 && D >= 1 && D <= 366)
#define LOG_SID(FUNC, format,...)\
do {\
@@ -30,43 +38,151 @@ do {\
char s[6];\
char c[6];\
char l[6];\
ms_strncpclean(n, head.network, 2);\
ms_strncpclean(s, head.station, 5);\
ms_strncpclean(l, head.location, 2);\
ms_strncpclean(c, head.channel, 3);\
ms_strncpclean(n, pMS2FSDH_NETWORK(head), 2);\
ms_strncpclean(s, pMS2FSDH_STATION(head), 5);\
ms_strncpclean(l, pMS2FSDH_LOCATION(head), 2);\
ms_strncpclean(c, pMS2FSDH_CHANNEL(head), 3);\
FUNC("[%s.%s.%s.%s] " format, n, s, l, c, ##__VA_ARGS__);\
} while(0)
}
namespace Gempa {
namespace CAPS {
namespace {
uint16_t ms2_blktlen(uint16_t blkttype, const char *blkt, int8_t swapflag) {
uint16_t blktlen = 0;
switch ( blkttype ) {
case 100: // Sample Rate
blktlen = 12;
break;
case 200: // Generic Event Detection
blktlen = 28;
break;
case 201: // Murdock Event Detection
blktlen = 36;
break;
case 300: // Step Calibration
blktlen = 32;
break;
case 310: // Sine Calibration
blktlen = 32;
break;
case 320: // Pseudo-random Calibration
blktlen = 28;
break;
case 390: // Generic Calibration
blktlen = 28;
break;
case 395: // Calibration Abort
blktlen = 16;
break;
case 400: // Beam
blktlen = 16;
break;
case 500: // Timing
blktlen = 8;
break;
case 1000: // Data Only SEED
blktlen = 8;
break;
case 1001: // Data Extension
blktlen = 8;
break;
case 2000: // Opaque Data
// First 2-byte field after the blockette header is the length
if ( blkt ) {
blktlen = *reinterpret_cast<const uint16_t*>(blkt + 4);
if ( swapflag ) {
ms_gswap2(&blktlen);
}
}
break;
}
return blktlen;
}
bool double2frac(uint16_t &numerator, uint16_t &denominator, double value) {
using namespace std;
// Check numeric limits
if ( value > numeric_limits<uint16_t>::max() ) {
numerator = numeric_limits<uint16_t>::max();
denominator = 1;
return false;
}
if ( value < numeric_limits<uint16_t>::min() ) {
numerator = numeric_limits<uint16_t>::min();
denominator = 1;
return false;
}
// Operate on positive numbers
int sign = 1;
if ( value < 0 ) {
sign = -1;
value = -value;
}
// Calculatate the largest possible power of 10 giving numeric integer
// limits and the current input number
static auto max_exp = floor(log10(numeric_limits<uint16_t>::max())) - 1.0;
auto exp = max_exp;
if ( value >= 10 ) {
exp -= floor(log10(value));
}
// Expand input number with power of 10
denominator = static_cast<int>(pow(10, exp));
numerator = static_cast<int>(round(value * denominator));
// Simplify the fraction by calculating the greatest common divisor
int gcd_ = gcd(numerator, denominator);
numerator = sign * numerator / gcd_;
denominator = denominator / gcd_;
return true;
}
}
MSEEDDataRecord::MSEEDDataRecord() {}
DataRecord *MSEEDDataRecord::clone() const {
return new MSEEDDataRecord(*this);
}
const char *MSEEDDataRecord::formatName() const {
return "MSEED";
}
bool MSEEDDataRecord::readMetaData(std::streambuf &buf, int size,
Header &header,
Time &startTime,
Time &endTime) {
fsdh_s head;
Header &header, Time &startTime, Time &endTime) {
char head[48];
if ( size <= 0 ) {
CAPS_WARNING("read metadata: invalid size of record: %d", size);
return false;
}
if ( size < MINRECLEN || size > MAXRECLEN ) {
if ( (size < MINRECLEN) || (size > MAXRECLEN) ) {
CAPS_WARNING("read metadata: invalid MSEED record size: %d", size);
return false;
}
// Read first 32 byte
// Read first 40 byte
size_t read = buf.sgetn((char*)&head, sizeof(head));
if ( read < sizeof(head) ) {
CAPS_WARNING("read metadata: input buffer underflow: only %d/%d bytes read",
@@ -74,172 +190,249 @@ bool MSEEDDataRecord::readMetaData(std::streambuf &buf, int size,
return false;
}
if ( !MS_ISVALIDHEADER(((char*)&head)) ) {
if ( MS2_ISVALIDHEADER(((char*)&head)) ) {
int headerswapflag = 0;
// Swap byte order?
if ( !MS_ISVALIDYEARDAY(*pMS2FSDH_YEAR(head), *pMS2FSDH_DAY(head)) ) {
headerswapflag = 1;
ms_gswap2(pMS2FSDH_YEAR(head));
ms_gswap2(pMS2FSDH_DAY(head));
ms_gswap2(pMS2FSDH_FSEC(head));
ms_gswap2(pMS2FSDH_NUMSAMPLES(head));
ms_gswap2(pMS2FSDH_SAMPLERATEFACT(head));
ms_gswap2(pMS2FSDH_SAMPLERATEMULT(head));
ms_gswap4(pMS2FSDH_TIMECORRECT(head));
ms_gswap2(pMS2FSDH_DATAOFFSET(head));
ms_gswap2(pMS2FSDH_BLOCKETTEOFFSET(head));
}
auto year = *pMS2FSDH_YEAR(head);
auto yday = *pMS2FSDH_DAY(head);
auto hours = *pMS2FSDH_HOUR(head);
auto minutes = *pMS2FSDH_MIN(head);
auto seconds = *pMS2FSDH_SEC(head);
auto fsec = *pMS2FSDH_FSEC(head) * 100;
header.dataType = DT_Unknown;
startTime = Time::FromYearDay(year, yday);
startTime += TimeSpan(hours * 3600 + minutes * 60 + seconds, fsec);
header.quality.ID = 0;
header.quality.str[0] = *pMS2FSDH_DATAQUALITY(head);
if ( *pMS2FSDH_TIMECORRECT(head) != 0 && !(*pMS2FSDH_ACTFLAGS(head) & 0x02) ) {
startTime += TimeSpan(0, *pMS2FSDH_TIMECORRECT(head) * 100);
}
// Parse blockettes
uint32_t blkt_offset = *pMS2FSDH_BLOCKETTEOFFSET(head);
uint32_t blkt_length;
uint16_t blkt_type;
uint16_t next_blkt;
if ( blkt_offset < sizeof(head) ) {
LOG_SID(CAPS_DEBUG, "%s", "read metadata: blockette "
"offset points into header");
return false;
}
uint32_t coffs = 0;
while ( (blkt_offset != 0) && ((int)blkt_offset < size) &&
(blkt_offset < static_cast<uint32_t>(size)) ) {
char bhead[6];
int seek_ofs = blkt_offset - sizeof(head) - coffs;
buf.pubseekoff(seek_ofs, std::ios_base::cur, std::ios_base::in);
coffs += seek_ofs;
if ( buf.sgetn(bhead, 6) != 6 ) {
LOG_SID(CAPS_DEBUG, "%s", "read metadata: "
"failed to read blockette header");
break;
}
coffs += 6;
memcpy(&blkt_type, bhead, 2);
memcpy(&next_blkt, bhead + 2, 2);
if ( headerswapflag ) {
ms_gswap2(&blkt_type);
ms_gswap2(&next_blkt);
}
blkt_length = ms2_blktlen(blkt_type, bhead, headerswapflag);
if ( blkt_length == 0 ) {
LOG_SID(CAPS_DEBUG, "read metadata: "
"unknown blockette length for type %d",
blkt_type);
break;
}
/* Make sure blockette is contained within the msrecord buffer */
if ( (int)(blkt_offset - 4 + blkt_length) > size ) {
LOG_SID(CAPS_DEBUG, "read metadata: blockette "
"%d extends beyond record size, truncated?",
blkt_type);
break;
}
if ( blkt_type == 1000 ) {
switch ( (int)bhead[4] ) {
case DE_ASCII:
header.dataType = DT_INT8;
break;
case DE_INT16:
header.dataType = DT_INT16;
break;
case DE_INT32:
case DE_STEIM1:
case DE_STEIM2:
case DE_CDSN:
case DE_DWWSSN:
case DE_SRO:
header.dataType = DT_INT32;
break;
case DE_FLOAT32:
header.dataType = DT_FLOAT;
break;
case DE_FLOAT64:
header.dataType = DT_DOUBLE;
break;
case DE_GEOSCOPE24:
case DE_GEOSCOPE163:
case DE_GEOSCOPE164:
header.dataType = DT_FLOAT;
break;
default:
break;
}
}
else if ( blkt_type == 1001 ) {
// Add usec correction
startTime += TimeSpan(0, *reinterpret_cast<int8_t*>(bhead + 5));
}
/* Check that the next blockette offset is beyond the current blockette */
if ( next_blkt && next_blkt < (blkt_offset + blkt_length - 4) ) {
LOG_SID(CAPS_DEBUG, "read metadata: offset to "
"next blockette (%d) is within current blockette "
"ending at byte %d",
blkt_type, (blkt_offset + blkt_length - 4));
break;
}
/* Check that the offset is within record length */
else if ( next_blkt && next_blkt > size ) {
LOG_SID(CAPS_DEBUG, "read metadata: offset to "
"next blockette (%d) from type %d is beyond record "
"length", next_blkt, blkt_type);
break;
}
else
blkt_offset = next_blkt;
}
endTime = startTime;
if ( *pMS2FSDH_SAMPLERATEFACT(head) > 0 ) {
header.samplingFrequencyNumerator = *pMS2FSDH_SAMPLERATEFACT(head);
header.samplingFrequencyDenominator = 1;
}
else {
header.samplingFrequencyNumerator = 1;
header.samplingFrequencyDenominator = -*pMS2FSDH_SAMPLERATEFACT(head);
}
if ( *pMS2FSDH_SAMPLERATEMULT(head) > 0 ) {
header.samplingFrequencyNumerator *= *pMS2FSDH_SAMPLERATEMULT(head);
}
else {
header.samplingFrequencyDenominator *= -*pMS2FSDH_SAMPLERATEMULT(head);
}
if ( header.samplingFrequencyNumerator > 0.0 && *pMS2FSDH_NUMSAMPLES(head) > 0 ) {
int64_t dt = static_cast<int64_t>(*pMS2FSDH_NUMSAMPLES(head)) * 1000000 * header.samplingFrequencyDenominator / header.samplingFrequencyNumerator;
endTime += TimeSpan(0, dt);
}
timeToTimestamp(header.samplingTime, startTime);
}
else if ( MS3_ISVALIDHEADER(((char*)&head)) ) {
startTime = Time::FromYearDay(
Endianess::Converter::ToLittleEndian(*pMS3FSDH_YEAR(head)),
Endianess::Converter::ToLittleEndian(*pMS3FSDH_DAY(head))
);
startTime += TimeSpan(
Endianess::Converter::ToLittleEndian(*pMS3FSDH_HOUR(head)) * 3600 +
Endianess::Converter::ToLittleEndian(*pMS3FSDH_MIN(head)) * 60 +
Endianess::Converter::ToLittleEndian(*pMS3FSDH_SEC(head)),
Endianess::Converter::ToLittleEndian(*pMS3FSDH_NSEC(head)) / 1000
);
auto nsamp = Endianess::Converter::ToLittleEndian(*pMS3FSDH_NUMSAMPLES(head));
auto fsamp = Endianess::Converter::ToLittleEndian(*pMS3FSDH_SAMPLERATE(head));
if ( fsamp < 0 ) {
fsamp = -1.0 / fsamp;
}
auto encoding = Endianess::Converter::ToLittleEndian(*pMS3FSDH_ENCODING(head));
header.dataType = DT_Unknown;
switch ( encoding ) {
case DE_ASCII:
header.dataType = DT_INT8;
break;
case DE_INT16:
header.dataType = DT_INT16;
break;
case DE_INT32:
case DE_STEIM1:
case DE_STEIM2:
case DE_CDSN:
case DE_DWWSSN:
case DE_SRO:
header.dataType = DT_INT32;
break;
case DE_FLOAT32:
header.dataType = DT_FLOAT;
break;
case DE_FLOAT64:
header.dataType = DT_DOUBLE;
break;
case DE_GEOSCOPE24:
case DE_GEOSCOPE163:
case DE_GEOSCOPE164:
header.dataType = DT_FLOAT;
break;
default:
break;
}
header.quality.ID = 0;
if ( !double2frac(header.samplingFrequencyNumerator, header.samplingFrequencyDenominator, fsamp) ) {
CAPS_WARNING("read metadata: invalid sampling rate: %f", fsamp);
return false;
}
endTime = startTime;
if ( header.samplingFrequencyNumerator > 0.0 && *pMS3FSDH_NUMSAMPLES(head) > 0 ) {
int64_t dt = static_cast<int64_t>(nsamp) * 1000000 * header.samplingFrequencyDenominator / header.samplingFrequencyNumerator;
endTime += TimeSpan(0, dt);
}
timeToTimestamp(header.samplingTime, startTime);
return false;
}
else {
CAPS_WARNING("read metadata: invalid MSEED header");
return false;
}
bool headerswapflag = false;
if ( !MS_ISVALIDYEARDAY(head.start_time.year, head.start_time.day) )
headerswapflag = true;
/* Swap byte order? */
if ( headerswapflag ) {
MS_SWAPBTIME(&head.start_time);
ms_gswap2a(&head.numsamples);
ms_gswap2a(&head.samprate_fact);
ms_gswap2a(&head.samprate_mult);
ms_gswap4a(&head.time_correct);
ms_gswap2a(&head.data_offset);
ms_gswap2a(&head.blockette_offset);
}
header.dataType = DT_Unknown;
hptime_t hptime = ms_btime2hptime(&head.start_time);
if ( hptime == HPTERROR ) {
LOG_SID(CAPS_DEBUG, "read metadata: invalid start time");
return false;
}
header.quality.ID = 0;
header.quality.str[0] = head.dataquality;
if ( head.time_correct != 0 && !(head.act_flags & 0x02) )
hptime += (hptime_t)head.time_correct * (HPTMODULUS / 10000);
// Parse blockettes
uint32_t blkt_offset = head.blockette_offset;
uint32_t blkt_length;
uint16_t blkt_type;
uint16_t next_blkt;
if ( blkt_offset < sizeof(head) ) {
LOG_SID(CAPS_DEBUG, "read metadata: blockette "
"offset points into header");
return false;
}
uint32_t coffs = 0;
while ( (blkt_offset != 0) && ((int)blkt_offset < size) &&
(blkt_offset < MAXRECLEN) ) {
char bhead[6];
int seek_ofs = blkt_offset-sizeof(head)-coffs;
buf.pubseekoff(seek_ofs, std::ios_base::cur, std::ios_base::in);
coffs += seek_ofs;
if ( buf.sgetn(bhead, 6) != 6 ) {
LOG_SID(CAPS_DEBUG, "read metadata: "
"failed to read blockette header");
break;
}
coffs += 6;
memcpy(&blkt_type, bhead, 2);
memcpy(&next_blkt, bhead+2, 2);
if ( headerswapflag ) {
ms_gswap2(&blkt_type);
ms_gswap2(&next_blkt);
}
blkt_length = ms_blktlen(blkt_type, bhead, headerswapflag);
if ( blkt_length == 0 ) {
LOG_SID(CAPS_DEBUG, "read metadata: "
"unknown blockette length for type %d",
blkt_type);
break;
}
/* Make sure blockette is contained within the msrecord buffer */
if ( (int)(blkt_offset - 4 + blkt_length) > size ) {
LOG_SID(CAPS_DEBUG, "read metadata: blockette "
"%d extends beyond record size, truncated?",
blkt_type);
break;
}
if ( blkt_type == 1000 ) {
switch ( (int)bhead[4] ) {
case DE_ASCII:
header.dataType = DT_INT8;
break;
case DE_INT16:
header.dataType = DT_INT16;
break;
case DE_INT32:
case DE_STEIM1:
case DE_STEIM2:
case DE_CDSN:
case DE_DWWSSN:
case DE_SRO:
header.dataType = DT_INT32;
break;
case DE_FLOAT32:
header.dataType = DT_FLOAT;
break;
case DE_FLOAT64:
header.dataType = DT_DOUBLE;
break;
case DE_GEOSCOPE24:
case DE_GEOSCOPE163:
case DE_GEOSCOPE164:
header.dataType = DT_FLOAT;
break;
default:
break;
}
}
else if ( blkt_type == 1001 ) {
// Add usec correction
hptime += ((hptime_t)bhead[5]) * (HPTMODULUS / 1000000);
}
/* Check that the next blockette offset is beyond the current blockette */
if ( next_blkt && next_blkt < (blkt_offset + blkt_length - 4) ) {
LOG_SID(CAPS_DEBUG, "read metadata: offset to "
"next blockette (%d) is within current blockette "
"ending at byte %d",
blkt_type, (blkt_offset + blkt_length - 4));
break;
}
/* Check that the offset is within record length */
else if ( next_blkt && next_blkt > size ) {
LOG_SID(CAPS_DEBUG, "read metadata: offset to "
"next blockette (%d) from type %d is beyond record "
"length", next_blkt, blkt_type);
break;
}
else
blkt_offset = next_blkt;
}
startTime = Time((hptime_t)hptime/HPTMODULUS,(hptime_t)hptime%HPTMODULUS);
endTime = startTime;
if ( head.samprate_fact > 0 ) {
header.samplingFrequencyNumerator = head.samprate_fact;
header.samplingFrequencyDenominator = 1;
}
else {
header.samplingFrequencyNumerator = 1;
header.samplingFrequencyDenominator = -head.samprate_fact;
}
if ( head.samprate_mult > 0 )
header.samplingFrequencyNumerator *= head.samprate_mult;
else
header.samplingFrequencyDenominator *= -head.samprate_mult;
if ( header.samplingFrequencyNumerator > 0.0 && head.numsamples > 0 ) {
hptime = (hptime_t)head.numsamples * HPTMODULUS * header.samplingFrequencyDenominator / header.samplingFrequencyNumerator;
endTime += TimeSpan((hptime_t)hptime/HPTMODULUS,(hptime_t)hptime%HPTMODULUS);
}
timeToTimestamp(header.samplingTime, startTime);
return true;
}
@@ -319,68 +512,19 @@ bool MSEEDDataRecord::put(std::streambuf &buf, bool /*withHeader*/) const {
return buf.sputn(&_data[0], _data.size()) == (int)_data.size();
}
void MSEEDDataRecord::setData(const void *data, size_t size) {
_data.resize(size);
memcpy(_data.data(), data, size);
unpackHeader();
}
void MSEEDDataRecord::unpackHeader(char *data, size_t size) {
// Only unpack the header structure
MSRecord *ms_rec = NULL;
int state = msr_unpack(data, size, &ms_rec, 0, 0);
if ( state != MS_NOERROR ) {
CAPS_WARNING("read metadata: read error: %d", state);
if ( ms_rec != NULL )
msr_free(&ms_rec);
return;
void MSEEDDataRecord::unpackHeader() {
arraybuf tmp(_data.data(), _data.size());
if ( !readMetaData(tmp, _data.size(), _header, _startTime, _endTime) ) {
CAPS_WARNING("invalid MSEED header");
}
hptime_t hptime = msr_starttime(ms_rec);
_startTime = Time((hptime_t)hptime/HPTMODULUS,(hptime_t)hptime%HPTMODULUS);
_endTime = _startTime;
if ( ms_rec->samprate > 0.0 && ms_rec->samplecnt > 0 ) {
hptime = (hptime_t)(((double)(ms_rec->samplecnt) / ms_rec->samprate * HPTMODULUS) + 0.5);
_endTime += TimeSpan((hptime_t)hptime/HPTMODULUS,(hptime_t)hptime%HPTMODULUS);
}
_header.dataType = DT_Unknown;
timeToTimestamp(_header.samplingTime, _startTime);
if ( ms_rec->fsdh->samprate_fact > 0 ) {
_header.samplingFrequencyNumerator = ms_rec->fsdh->samprate_fact;
_header.samplingFrequencyDenominator = 1;
}
else {
_header.samplingFrequencyNumerator = 1;
_header.samplingFrequencyDenominator = -ms_rec->fsdh->samprate_fact;
}
if ( ms_rec->fsdh->samprate_mult > 0 )
_header.samplingFrequencyNumerator *= ms_rec->fsdh->samprate_mult;
else
_header.samplingFrequencyDenominator *= -ms_rec->fsdh->samprate_mult;
switch ( ms_rec->sampletype ) {
case 'a':
_header.dataType = DT_INT8;
break;
case 'i':
_header.dataType = DT_INT32;
break;
case 'f':
_header.dataType = DT_FLOAT;
break;
case 'd':
_header.dataType = DT_DOUBLE;
break;
default:
_header.dataType = DT_Unknown;
break;
}
msr_free(&ms_rec);
}