/****************************************************************************/ /* */ /* IAGA.h */ /* */ /****************************************************************************/ /****************************************************************************/ /* This is a C language header file that defines the following routines */ /* for reading IAGA format data files into memory and writing data from */ /* memory into IAGA-format data files. The description of IAGA format is */ /* given in file 'IAGA.doc'. */ /* */ /* ------------------- Read data from a IAGA-format file -------------- */ /* */ /* long ReadIAGA(char *FileName,NetworkPtr Network,char *StartTimeStr, */ /* char *EndTimeStr,char *StationList) */ /* FileName Name of the IAGA-file containing the data. */ /* If FileName is nil or zero length then standard */ /* input is used. */ /* Network Structure into which the data is read. */ /* Network_struct is defined in file MagnData.h. */ /* StartTimeStr String defining the first time to be read from */ /* the data file (String delimited with '\0'). */ /* String format : YYMMDDHH. If HH is missing then */ /* 00 is assumed. If StartTimeStr is nil or zero */ /* length then start of file is assumed. */ /* EndTimeStr String defining the time not to be read anymore. */ /* Format as before YYMMDDHH. If nil or zero length */ /* then data is read until end of file. */ /* StationList List of stations included in the data. The stations */ /* must be specified with three letter codes */ /* (e.g. SOR) and must be separeted with a space in */ /* the list (e.g. 'SOR MAS KEV'). If nil or zero */ /* length then all stations in the file are included. */ /* */ /* The function result indicates how successfull the file reading was: */ /* 0: OK : File read successfully */ /* 1: FileError : Failed to read the given file */ /* 2: OutOfMemory : Memory allocation failed during reading */ /* 3: FormatError : File to be read is not IAGA file */ /* */ /* ------------------- Write data into a IAGA-format file -------------- */ /* */ /* long WriteIAGA(char *FileName,NetworkPtr Network,char *StartTimeStr, */ /* char *EndTimeStr,char *StationList) */ /* FileName Name of the IAGA file. IF NULL or zero length then */ /* stdout is used. */ /* Network Pointer to the Network structure whose data is to */ /* be written into a file. */ /* StartTimeStr Start date string YYMMDDHHMM. If NULL then same as */ /* the time of the first record of the first station */ /* in the Network data structure. */ /* EndTimeStr End date string YYMMDDHHMM. This record is NOT */ /* included anymore. If NULL then data till the end of */ /* first station in Network structure will be written. */ /* StationList String containing the 3-letter ID's of the stations */ /* to be included. If NULL or 0-length then all */ /* stations will be written. The stations must be */ /* separated by a space in the list (e.g. 'SOR KEV'). */ /* */ /* The function result indicates how successfull the file writing was: */ /* 0: OK : Data written successfully */ /* 1: FileError : Failed to create the given file */ /* */ /****************************************************************************/ /****************************************************************************/ /* Lasse Hakkinen */ /* Finnish Meteorological Institute */ /* Geophysical Research Division */ /* P.O.Box 503 */ /* FIN-00101, Helsinki, Finland */ /* e-mail: Lasse.Hakkinen@fmi.fi */ /* phone : (+358)-9-19294634 */ /* fax : (+358)-9-19294603 */ /* */ /* version 1.12 03.01.2020 */ /****************************************************************************/ /****************************************************************************/ /* Version history: */ /* */ /* 1.12 03.01.2020 Date strings must be YYYYMMDD (four digits for year) */ /* Replaced StrToSecs -> StrToSecsC and */ /* SecsToStr -> SecsToStrC. */ /* 1.11 19.11.2019 Added zero file size checking */ /* 1.10 12.05.2005 Fixed a bug in Reading an IAGA file in the case that */ /* there were missing data blocks in the IAGA file. */ /* 1.09 22.01.2001 Modified the computation of hourly averages so that if */ /* one or more data points are missing then the whole hour */ /* average is marked as missing. */ /* 1.08 04.05.2000 Fixed a bug in WriteIAGA which caused the field values */ /* to be written with more that 6 characters if the field */ /* value was larger than 999999. */ /* 1.07 08.03.2000 Added function Get5chars. This fixed a bug where the */ /* latitude of the station was incorrectly read if the */ /* longitude was more than 100 degrees (= all 5 chars) */ /* 1.06 20.10.1998 Slight change how missing data points are handled */ /* internally. Defined the IAGAMissingValue constant */ /* (!= MissingValue defined in MagnData.h. */ /* 1.05 16.10.1998 Fixed a bug where some data was not read in if */ /* StartTime was not exactly the same as IAGA block start */ /* time. */ /* 1.04 09.11.1996 Fixed a bug which caused WriteIAGA to crash if the */ /* StartTime or EndTime were not aligned along suitable */ /* minute boundaries. */ /* 1.03 11.09.1996 Fixed a bug where Temperature values where incorrectly */ /* written if Temp > 999 or Temp < -999 (physically */ /* unrealistic). */ /* 1.02 27.07.1996 Fixed a bug where WriteIAGA crashed is there is no data */ /* in the Network-structure */ /* 1.01 19.02.1996 Added WriteIAGABlock routine */ /* 1.0 13.11.1995 First official release */ /****************************************************************************/ #include #include #include /*--------------------------------------------------------------------------*/ /* Define the offsets of various fields from the start of the IAGA block */ /* See the definition of IAGA record for the explanation of various fields. */ /*--------------------------------------------------------------------------*/ #define IAGA_RecLength 0 #define IAGA_RecLenMin 4 #define IAGA_Datatype 7 #define IAGA_StationNbr 9 #define IAGA_StationID 12 #define IAGA_Latitude 15 #define IAGA_Longitude 20 #define IAGA_Free1 25 #define IAGA_DateCentury 48 #define IAGA_DateYear 50 #define IAGA_DateMonth 52 #define IAGA_DateDay 54 #define IAGA_DateHour 56 #define IAGA_DateMinute 58 #define IAGA_SampleStep 60 #define IAGA_HowProduced 62 #define IAGA_FilterBreak 63 #define IAGA_FilterSlope 67 #define IAGA_BaselineInfo 69 #define IAGA_BaselineChange 70 #define IAGA_Components 72 #define IAGA_DayCharacter 73 #define IAGA_Free2 74 #define IAGA_Temperature 74 #define IAGA_Data 159 #define IAGA_DataAverage 1419 #define IAGAMissingValue 999999 /*--------------------------------------------------------------------------*/ /* Function prototypes */ /*--------------------------------------------------------------------------*/ Time_sec GetIAGATime(char *Buffer); static long GetIAGAStep(char *Buffer); int ReadIAGABlock(FILE *IAGAfile,char *Buff); static long Get5chars(char *p); static void SetStationParamsIAGA(StationPtr Station,char *Buff); long GetIAGAFieldValue(char *p); static long GetIAGATempValue(char *Buff); static void GetIAGAData(StationPtr Station, char *Buff); long ReadIAGA(char *FileName,NetworkPtr Network,char *StartTimeStr, char *EndTimeStr,char *StationList); void WriteIAGABlock(FILE *DataFile,char *Buffer); static void WriteIAGAdata(FILE *DataFile,StationPtr Station,Time_sec Time); static void WriteIAGAaverages(FILE *DataFile, StationPtr Station,Time_sec Time); long WriteIAGA(char *FileName,NetworkPtr Network,char *StartTimeStr, char *EndTimeStr,char *StationList); /*==========================================================================*/ /* Routines for reading IAGA-format files */ /*==========================================================================*/ /*--------------------------------------------------------------------------*/ /* Get the start time in seconds of a 1440-character data block. In the */ /* IAGA file the date is in format YYYYMMDDHHMM. */ /*--------------------------------------------------------------------------*/ Time_sec GetIAGATime(char *Buffer) { // return (StrToSecs(Buffer+IAGA_DateYear,10)); return (StrToSecsC(Buffer+IAGA_DateCentury,12)); } /*--------------------------------------------------------------------------*/ /* Get the 2 character sample step value from the buffer. */ /*--------------------------------------------------------------------------*/ static long GetIAGAStep(char *Buffer) { char *q = Buffer+IAGA_SampleStep; return (10*(*q-48)+(*(q+1)-48)); } /*--------------------------------------------------------------------------*/ /* Read one block from the IAGA-format file into the Buffer. This routine */ /* works also for 1441-character blocks (i.e. terminated with linefeeds). */ /* The routine will return 1 if the block was succesfully read, otherwise */ /* 0 is returned. */ /*--------------------------------------------------------------------------*/ int ReadIAGABlock(FILE *IAGAfile,char *Buff) { int c; /* Handle the first character */ if ((c = getc(IAGAfile)) == EOF) return(0); if (c < 32) { /* Linefeed or carriage return */ if ((c = getc(IAGAfile)) == EOF) return(0); } *Buff = c; /* Put the first char into Buffer */ /* read the rest of the block */ return(fread(Buff+1,1439,1,IAGAfile)); } /*--------------------------------------------------------------------------*/ /* Get the 5 character long from a Buffer. */ /*--------------------------------------------------------------------------*/ static long Get5chars(char *p) { int k; long value = 0; long negative = 0; if (*p == '-') negative = 1; else if (*p != ' ') value = *p-'0'; p++; /* Get the rest 4 characters */ for (k=0;k<4;k++) { if (*p != ' ') value = 10*value+(*p-'0'); p++; } if (negative) value = -value; return (value); } /*--------------------------------------------------------------------------*/ /* Fill some fields in the Station_struct by copying them from the IAGA */ /* buffer. AddNewStation procedure in the MagnData.h file will set other */ /* station parameters. */ /*--------------------------------------------------------------------------*/ static void SetStationParamsIAGA(StationPtr Station,char *Buff) { /* Set the station ID */ strncpy(Station->StationID,Buff+IAGA_StationID,3); Station->StationID[3] = '\0'; /* Set the location of the station */ Station->Latitude = Get5chars(Buff+IAGA_Latitude); Station->Longitude = Get5chars(Buff+IAGA_Longitude); /* Set the StartTime and TimeStep, GetIAGAData-routine will set EndTime */ Station->StartTime = GetIAGATime(Buff); Station->TimeStep = GetIAGAStep(Buff); Station->EndTime = Station->StartTime; /* Set the component markers */ if (*(Buff+IAGA_Components) == '1') strcpy(Station->Components,"XYZ"); if (*(Buff+IAGA_Components) == '2') strcpy(Station->Components,"HDZ"); } /*--------------------------------------------------------------------------*/ /* Get the 1+6 character number from the Buffer. We could also use sscanf, */ /* but this is considerably faster because no checkings are made. */ /*--------------------------------------------------------------------------*/ long GetIAGAFieldValue(char *p) { int k; long value = 0; long negative; negative = (*p++ == '-'); /* Get the 6-characters */ for (k=0;k<6;k++) { if (*p != ' ') value = 10*value+(*p-'0'); p++; } if (negative) value = -value; if (value == IAGAMissingValue) return (MissingValue); else return (value); } /*--------------------------------------------------------------------------*/ /* Get the 1+3 character temperature value from the buffer. The temperature */ /* value is stored in the free-area starting from byte 74 in the buffer. */ /* Unit of temperature is 0.1 degrees centigrade. */ /*--------------------------------------------------------------------------*/ static long GetIAGATempValue(char *Buff) { char *q = Buff+IAGA_Temperature+1; /* first digit after sign */ long k = 0; long i; if (*(Buff+IAGA_Temperature+3) == ' ') /* If last digit is missing */ return (MissingValue); /* then whole value is missing */ for (i=0;i<3;i++) k = 10*k+(*q++)-'0'; if (k == 999) return (MissingValue); if (*(Buff+74) == '-') return (-k); else return (k); } /*--------------------------------------------------------------------------*/ /* Copy the data from the 1440 character block into the proper OneDayBlock. */ /*--------------------------------------------------------------------------*/ static void GetIAGAData(StationPtr Station, char *Buff) { OneDay_struct *p; long *XPtr,*YPtr,*ZPtr,*TPtr,*HPtr,*DPtr; long i; char *q; /* Find the last OneDayBlock */ for (p = Station->FirstDay; p->NextDay != NULL; p = p->NextDay); /* Set the temperature value */ TPtr = (p->T)+(p->TCount); *TPtr = GetIAGATempValue(Buff); p->TCount++; /* Set the pointers within the OneDayBlock */ XPtr = (p->X)+(p->XCount); YPtr = (p->Y)+(p->YCount); ZPtr = (p->Z)+(p->ZCount); /* Copy the data */ q = Buff+IAGA_Data; for (i=0;i<60;i++) { *XPtr++ = GetIAGAFieldValue(q); q += 7; *YPtr++ = GetIAGAFieldValue(q); q += 7; *ZPtr++ = GetIAGAFieldValue(q); q += 7; } /* If components are HDZ then copy the data to HD area */ if (strncmp(Station->Components, "HDZ",3) == 0) { XPtr = (p->X)+(p->XCount); YPtr = (p->Y)+(p->YCount); HPtr = (p->H)+(p->XCount); DPtr = (p->D)+(p->YCount); for (i=0;i<60;i++) { *HPtr++ = *XPtr++; *DPtr++ = *YPtr++; } } p->XCount += 60; p->YCount += 60; p->ZCount += 60; /* Set the last time */ Station->EndTime = GetIAGATime(Buff)+60*Station->TimeStep; } /*--------------------------------------------------------------------------*/ /* Add missing data block */ /*--------------------------------------------------------------------------*/ static void AddMissingBlock(StationPtr Station) { OneDay_struct *p; /* Find the last OneDayBlock */ for (p = Station->FirstDay; p->NextDay != NULL; p = p->NextDay); /* We only have to update counters since the data areas are already */ /* initialized with missing data */ p->XCount += 60; p->YCount += 60; p->ZCount += 60; p->TCount += 1; } /*--------------------------------------------------------------------------*/ /* Here is the routine for reading IAGA-format data file into memory. */ /* The data is read into a Network structure (see MagnData.h for details). */ /* See the comments at the beginning of this file for more details about */ /* the parameters for this routine. */ /* */ /* The rounite works by reading the data file one block (1440 bytes) at a */ /* time and adds that data in the particular stations data block. First */ /* for each new station that is encountered space for one day is allocated.*/ /* If the day becomes full a new one day block is allocated. At the end of */ /* the routine all one day blocks are combined into a single data block. */ /*--------------------------------------------------------------------------*/ long ReadIAGA(char *FileName,NetworkPtr Network,char *StartTimeStr, char *EndTimeStr,char *StationList) { FILE *IAGAfile; /* The data file to be processed */ char Buffer[1440]; /* Buffer for one IAGA data block */ Time_sec Time; /* Time of the current data block */ Time_sec StartTime; /* Time of first record to be read */ Time_sec EndTime; /* Time of record not read anymore */ Time_sec CurrTime; /* Time of previous data block */ Time_sec TimeStep; /* Sampling rate in seconds */ StationPtr Station; /* Pointer to the current station */ long FileSize; /* Size of the IAGA FILE */ InitNetwork(Network); /* Initialize the fields of Network */ /* --- Set the start and end times --- */ if ((StartTimeStr == NULL) || (*StartTimeStr == '\0')) StartTime = MIN_TIME; else StartTime = StrToSecsC(StartTimeStr,0); if ((EndTimeStr == NULL) || (*EndTimeStr == '\0')) EndTime = MAX_TIME; else EndTime = StrToSecsC(EndTimeStr,0); /* --- Try to open the file --- */ if ((FileName == NULL) || (*FileName == '\0')) IAGAfile = stdin; else { // Try to open the file and check that its size !== 0 if ((IAGAfile = fopen(FileName, "r")) == NULL) return FileError; fseek(IAGAfile, 0, SEEK_END); // seek to end of file FileSize = ftell(IAGAfile); // get current file pointer fseek(IAGAfile, 0, SEEK_SET); // seek back to beginning of file if (FileSize == 0) return ZeroFileSize; } /* --- Read the data into one day blocks ---*/ while (ReadIAGABlock(IAGAfile,Buffer)) { if (strncmp(Buffer,"144",3) != 0) /* Check that this is IAGA file */ return(FileFormatError); if ((Time = GetIAGATime(Buffer)) >= EndTime) break; TimeStep = GetIAGAStep(Buffer); if (((Time+60*TimeStep) > StartTime) && StationInList(Buffer+IAGA_StationID,StationList)) { Station = FindStation(Network,Buffer+IAGA_StationID); if (Station == NULL) { /* First occurrence of this station */ Station = AddNewStation(Network,TimeStep,60*TimeStep); if (Station != NULL) SetStationParamsIAGA(Station,Buffer); else /* Unable to allocate memory for station data */ return(OutOfMemory); } /* --- Add possible missing data blocks --- */ /* CurrTime is the expected time of the current data block.*/ /* If it is less than the time read from the block then */ /* add missing data blocks */ CurrTime = Station->EndTime; while (CurrTime < Time) { AddMissingBlock(Station); if (OneDayFull(Station,'Z')) { if (AddOneDay(Station)) return(OutOfMemory); } CurrTime += 60*TimeStep; } if (OneDayFull(Station,'Z')) { if (AddOneDay(Station)) return(OutOfMemory); /* Failed to allocate memory */ } GetIAGAData(Station,Buffer); } } fclose(IAGAfile); /* --- Combine one day blocks into one large block ---*/ Station = Network->StationList; while (Station != NULL) { if (CombineData(Station) == OutOfMemory) return(OutOfMemory); Station = Station->Next; } return OK; } /*==========================================================================*/ /* Routines for writing IAGA-format files */ /*==========================================================================*/ /*--------------------------------------------------------------------------*/ /* Write one 1440 character block into standard output */ /*--------------------------------------------------------------------------*/ void WriteIAGABlock(FILE *DataFile,char *Buffer) { fwrite(Buffer,1440,1,DataFile); } /*--------------------------------------------------------------------------*/ /* Compute an average over speficied time period for specified station, */ /* component and time. If one or more data points are missing then the */ /* average is marked as missing. */ /*--------------------------------------------------------------------------*/ long ComputeHourAverage(StationPtr Station,char Comp,Time_sec Time,long Length) { double Sum = 0; /* we must use double as long might overflow */ long Good = 0; long i,Count,Value; if (Length == 0) Count = 1; /* Average is same as a single data value */ else Count = Length/Station->TimeStep; for (i=0;iTimeStep; } return RoundFloat(Sum/Good); } /*--------------------------------------------------------------------------*/ /* Write one 1440 character block for given station and for given time */ /* into specified datafile. All 1440 characters except the hour averages */ /* are written (WriteAverages routine does that). */ /*--------------------------------------------------------------------------*/ static void WriteIAGAdata(FILE *DataFile,StationPtr Station,Time_sec Time) { long j,Temperature; long X,Y,Z; StationInfoPtr p; char DummyStr[20]; /*** Find pointer to proper StationInfoStruct in StationInfoTable ***/ /*** defined in header file 'StatInfo.h' ***/ p = FindStationInfo(Station->StationID); /*** Fill out some fields with station info ***/ fprintf(DataFile,"1440%03d%s%s%s",Station->TimeStep, p->DataType,p->StationNum,Station->StationID); fprintf(DataFile,"% 05d%5d",Station->Latitude,Station->Longitude); fprintf(DataFile," "); /* 23 spaces */ // if (Time < YEAR2000) fprintf(DataFile,"19"); else fprintf(DataFile,"20"); // SecsToStr(Time,DummyStr); // fprintf(DataFile,"%.10s%02d",DummyStr,Station->TimeStep); SecsToStrC(Time,DummyStr); fprintf(DataFile,"%.12s%02d",DummyStr,Station->TimeStep); fprintf(DataFile,"%s%s%s",p->ProdType,p->FilterBreak,p->FilterSlope); fprintf(DataFile,"%s%s",p->BaseInfo,p->BaseChange); if (Station->Components[0] == 'X') fprintf(DataFile,"1"); else if (Station->Components[0] == 'H') fprintf(DataFile,"2"); fprintf(DataFile,"%s",p->DayCharacter); /*** Write temperature value ***/ Temperature = GetDataValue(Station,Time,'T'); if ((Temperature != MissingValue) && (Temperature < 999) && (Temperature > -999)) { fprintf(DataFile,"% 04d",Temperature); } else fprintf(DataFile," "); /*** Some empty space for comments etc. ***/ for (j=0;j<3;j++) /* 81 = 3*27 spaces */ fprintf(DataFile," "); /*** Write magnetometer data ***/ for (j=0;j<60;j++) { X = GetDataValue(Station,Time,'X'); Y = GetDataValue(Station,Time,'Y'); Z = GetDataValue(Station,Time,'Z'); if ((X == MissingValue) || (abs(X) > IAGAMissingValue)) X = IAGAMissingValue; if ((Y == MissingValue) || (abs(Y) > IAGAMissingValue)) Y = IAGAMissingValue; if ((Z == MissingValue) || (abs(Z) > IAGAMissingValue)) Z = IAGAMissingValue; fprintf(DataFile,"% 07d% 07d% 07d",X,Y,Z); Time += Station->TimeStep; } } /*--------------------------------------------------------------------------*/ /* Write hour averages */ /*--------------------------------------------------------------------------*/ static void WriteIAGAaverages(FILE *DataFile,StationPtr Stat,Time_sec Time) { Time_sec LastTime; /* Time of last record in the block+Step */ Time_struct TmTime; long AveX,AveY,AveZ; LastTime = Time+60*Stat->TimeStep; SecsToTm(LastTime,&TmTime); if (TmTime.tm_min == 0) { /* The last block of hour ? */ AveX = ComputeHourAverage(Stat,'X',LastTime-3600,3600); AveY = ComputeHourAverage(Stat,'Y',LastTime-3600,3600); AveZ = ComputeHourAverage(Stat,'Z',LastTime-3600,3600); if ((AveX == MissingValue) || (abs(AveX) > IAGAMissingValue)) AveX = IAGAMissingValue; if ((AveY == MissingValue) || (abs(AveY) > IAGAMissingValue)) AveY = IAGAMissingValue; if ((AveZ == MissingValue) || (abs(AveZ) > IAGAMissingValue)) AveZ = IAGAMissingValue; fprintf(DataFile,"% 07d",AveX); fprintf(DataFile,"% 07d",AveY); fprintf(DataFile,"% 07d",AveZ); } else fprintf(DataFile," 000000 000000 000000"); } /*--------------------------------------------------------------------------*/ /* Here is the routine for writing data into an IAGA-format file. See the */ /* comments at the beginning of this file for more details about the */ /* parameters for this routine. */ /*--------------------------------------------------------------------------*/ long WriteIAGA(char *FileName,NetworkPtr Network,char *StartTimeStr, char *EndTimeStr,char *StationList) { FILE *IAGAfile; /* The data file to be processed */ Time_sec Time; /* Time of the current data block */ Time_sec StartTime; /* Time of first record to be read */ Time_sec EndTime; /* Time of record not read anymore */ Time_sec IAGABlockLength; /* Length of one IAGA block in seconds */ StationPtr Station; /* Pointer to the current station */ /* --- Check there is some data in the Network --- */ if (Network->StationList == NULL) return FAIL; /* --- Set the start and end times --- */ if ((StartTimeStr == NULL) || (*StartTimeStr == '\0')) StartTime = Network->StationList[0].StartTime; else StartTime = StrToSecsC(StartTimeStr,0); if ((EndTimeStr == NULL) || (*EndTimeStr == '\0')) EndTime = Network->StationList[0].EndTime; else EndTime = StrToSecsC(EndTimeStr,0); /* --- Try to create the file --- */ if ((FileName == NULL) || (*FileName == '\0')) IAGAfile = stdout; else if ((IAGAfile = fopen(FileName, "w")) == NULL) return FileError; /* Set StartTime and EndTime so that IAGA blocks will */ /* start or end at suitable minute boundaries */ IAGABlockLength = 60*(Network->StationList[0].TimeStep); StartTime -= StartTime % IAGABlockLength; if ((EndTime % IAGABlockLength) != 0) EndTime += IAGABlockLength-(EndTime % IAGABlockLength); /* --- Write the data ---*/ Time = StartTime; while (Time < EndTime) { Station = Network->StationList; while (Station != NULL) { if (StationInList(Station->StationID,StationList)) { WriteIAGAdata(IAGAfile,Station,Time); WriteIAGAaverages(IAGAfile,Station,Time); } Station = Station->Next; } Time += IAGABlockLength; } fclose(IAGAfile); return OK; }