Пример #1
0
int main(int argc, char **argv)
{
	SEGYBinaryFileHeader reel;
	SEGYTraceHeader *header;
	char *dbin;
	char *outfile;
	FILE *fp;
	Pf *pf;
	Arr *channels;  /* channel order list */
	Arr *table_list;  /* array of valid tables */
	int nchan;
	char *stest;

	float **traces;
	char text_file_header[SEGY_TEXT_HEADER_SIZE];
	Dbptr db, trdb, dbj;
	Dbptr trdbss;
	int nsamp0;
	double time0, endtime0, samprate0;
	long int nsamp;
	double samprate;
	int i,j;
	char stime[30],etime[30];
	char s[128];
	double tlength;
	double phi, theta;
	char *newchan_standard[3]={"X1","X2","X3"};
	char *trsubset="chan=~/X./";
	char *newchan[3]={"R","T","Z"};
	Tbl *sortkeys=newtbl(0);
	char sta[10],chan[10];
	double lat, lon, elev, dnorth, deast, edepth;
	char segtype;
	char refsta[10];
	int total_traces=0;
	char *time_str;
	long int evid,shotid=1;
	int rotate=0;
	long int ntraces;
        int ichan;
	int map_to_cdp;  /* logical switch to output data like cdp stacked data */
	char *fmt="%Y %j %H %M %S %s";
	char *pfname;
	int Verbose=0;
	/* New features added 2009 */
	/* this is a boolean.  If true (nonzero) it is assumed stdin will
	contain four numbers:  time,lat, lon, elev.  If false, only the
	time field is read and remainder of any input on each line is dropped.*/
	int input_source_coordinates;
	/* scale factor for source coordinates.  Needed because segy uses
	an int to store source coordinates.  Sensible choices are
	3600 for arc seconds and 10000 for a pseudodecimal. Note this
	parameter is ignored unless input_source_coordinates is true.*/
	int coordScale;
	/* If true use passcal 32 bit extension num_samps as record length.
	SEGY standard uses a 16 bit entry that easily overflows with large
	shots at long offset.  In this ase assume the 16 bit quantity is
	meaningless. */
	int use_32bit_nsamp;
	/* This is switched on by argument switch.  When set to a nonzero
	(default) the reel headers are written.  When 0 `
	the reel headers will not be written -- used by seismic unix
	and passcal*/
	int write_reel_headers=1;

	/* SEG-Y version to output. Default is original 1975 spec (rev 0) */
	int16_t segy_format = SEGY_FORMAT_REV_0;

	/* dbsubset query string */
	char *substr=NULL;

	/* text_header_description is a buffer holding a user-supplied description
	 * to be placed in the 3200-byte text header block. It is controlled by
	 * the parameter file value text_header_description or by the -d command
	 * line option, with the latter taking precedence */
	char* text_header_description=NULL;

	if(argc < 3) usage();
	dbin = argv[1];
	outfile = argv[2];
	pfname = NULL;
	for(i=3;i<argc;++i)
	{
		if(!strcmp(argv[i],"-pf"))
		{
			++i;
			pfname = argv[i];
		}
		else if(!strcmp(argv[i],"-SU"))
		{
			write_reel_headers=0;
		}
		else if(!strcmp(argv[i],"-v"))
		{
			Verbose=1;
		}
		else if(!strcmp(argv[i],"-d"))
		{
			++i;
			text_header_description = strdup(argv[i]);
		}
		else if(!strcmp(argv[i],"-ss"))
		{
			++i;
			substr=argv[i];
		}
		else if(!strcmp(argv[i],"-V"))
		{
			++i;
			if     (!strcmp(argv[i],"0")) {segy_format = SEGY_FORMAT_REV_0;}
			else if(!strcmp(argv[i],"1")) {segy_format = SEGY_FORMAT_REV_1_0;}
			else if(!strcmp(argv[i],"SU"))
			{
				segy_format = SEGY_FORMAT_SU;
				write_reel_headers=0;
			}
			else
			{
				elog_complain(0, "SEG-Y Version must be either 1 or 0");
				usage();
			}
		}
		else
		{
			usage();
		}
	}
	/* Command-line parameter sanity checking */
	if (write_reel_headers==0 && segy_format != SEGY_FORMAT_SU){
		complain(0, "The SU option cannot be used with the -V option");
		usage();
	}
	if(pfname == NULL) pfname = strdup("db2segy");

	elog_init(argc, argv);

	if(pfread(pfname,&pf)) {
		elog_die(0,"pfread error for pf file %s.pf\n",argv[0]);
	}

	/* Read the text_header_description if we weren't passed the -d option */
	if (!text_header_description) {
		text_header_description=pfget_string(pf, "text_header_description");
	}

	/* rotation parameters */
	rotate=pfget_boolean(pf,"rotate");
	if(rotate)
	{
		phi = pfget_double(pf,"phi");
		theta = pfget_double(pf,"theta");
	}
	/* This function creates the channel order list keyed by
	station channel names */
	channels = build_stachan_list(pf,&nchan,Verbose);

	map_to_cdp = pfget_boolean(pf,"map_to_cdp");
	if(map_to_cdp && Verbose)
		elog_notify(0,"Casting data as CDP stacked section\n");
	if(dbopen(dbin,"r",&db) == dbINVALID)
	{
		elog_complain(1,"Cannot open db %s\n", dbin);
		usage();
	}
	/* We grab the sample rate and trace length (in seconds) and
	use this to define global sample rates for the data.
	SEG-Y REV0 REQUIRES fixed length records and sample rates, so
	irregular sample rates will cause this program to die.
	One could add a decimate/interpolate function, but this
	is not currently implemented */
	samprate0 = pfget_double(pf,"sample_rate");
	tlength = pfget_double(pf,"trace_length");
	nsamp0 = (int)(tlength*samprate0);
	use_32bit_nsamp=pfget_boolean(pf,"use_32bit_nsamp");
	if (ntohs(segy_format) >= 0x0100 && use_32bit_nsamp) {
		elog_complain(0,"The 32-bit extension field is incompatible with SEG-Y REV 1. Ignoring 'use_32bit_nsamp' from the parameter file");
		use_32bit_nsamp=0;
	}

	/* nsamp in segy is a 16 bit field.  Handling depends on
	setting of use_32bit_nsamp boolean */
	if(nsamp0 > SEGY_MAX_NSAMP)
	{
		if(use_32bit_nsamp)
		{
			elog_notify(0,"Warning:  segy uses a 16 bit entity to store number of samples\nThat field is garbage. Using the 32 bit extension field.");
		}
		else
		{
		elog_complain(0,
		  "Warning:  segy uses a 16 bit entity to store number of samples. Requested %d samples per trace.  Trucated to %d", nsamp0, SEGY_MAX_NSAMP);
		nsamp0 = SEGY_MAX_NSAMP;
		}
	}

	/* boolean.  When nonzero set coordinates as geographic arc seconds values */
	int use_geo_coordinates=pfget_boolean(pf,"use_geo_coordinates");

	/* boolean. When nonzero, output decimal degrees instead of arcseconds if
	 * the requested output format supports it (rev1 only) */
	int prefer_decimal_degrees=pfget_boolean(pf, "prefer_decimal_degrees");

	/* We now have enough information to decide the coordUnits for all traces */
	int coordUnits = 0;
	if (!use_geo_coordinates) {
		coordUnits=SEGY_TRACE_COORDUNITS_LENGTH;
	} else if (ntohs(segy_format) >= 0x0100 && prefer_decimal_degrees) {
		coordUnits=SEGY_TRACE_COORDUNITS_DECIMAL_DEGREES;
	} else {
		coordUnits=SEGY_TRACE_COORDUNITS_ARCSECONDS;
	}
	/* We should have set our coordinate units now */
	assert(coordUnits!=0);

	input_source_coordinates=pfget_boolean(pf,"input_source_coordinates");
	if(input_source_coordinates)
	{
		coordScale=pfget_int(pf,"coordinate_scale_factor");
	}
	else if (coordUnits==SEGY_TRACE_COORDUNITS_DECIMAL_DEGREES)
	{
		/* Use a sane scalar for decimal degrees. 10000 gives four decimal
		 * places of accuracy, which matches the CSS3.0 spec for lat and lon */
		coordScale=10000;
	}
	else
	{
		coordScale=1;
	}

	/* Print a diagnostic message if the user gave a sub-optimal value for the
	 * coordScale */
	if (coordUnits == SEGY_TRACE_COORDUNITS_DECIMAL_DEGREES &&
			coordScale < 10000)
	{
		elog_alert(0,
				"The supplied parameter 'coordinate_scale_factor' value of %d is less than 10000, and will cause loss of precision for decimal degree coordinates.",
				coordScale);
	}
    else if (coordUnits == SEGY_TRACE_COORDUNITS_ARCSECONDS)
    {
        if (coordScale > 1000) {
            elog_alert(0,
                    "The supplied parameter 'coordinate_scale_factor' value of %d is greater than 1000, and will cause loss of precision for arcsecond coordinates.",
                    coordScale);
        }
    }

	/* trace_gain_type: signed int */
	int16_t trace_gain_type = pfget_int(pf,"trace_gain_type");
	if (trace_gain_type < 0)
	{
		die(0, "The trace_gain_type must be zero or greater");
	}
	else
	{
		trace_gain_type=htons(trace_gain_type);
	}


	/* check list of tables defined in pf.  Return array of
	logicals that define which tables are valid and join
	tables. */
	table_list = check_tables(db,pf);
	check_for_required_tables(table_list);
	dbj = join_tables(db,pf,table_list);
	if(dbj.record == dbINVALID) elog_die(0,"dbjoin error\n");
	if(substr!=NULL) dbj=dbsubset(dbj,substr,0);
	long int ndbrows;
	dbquery(dbj,dbRECORD_COUNT,&ndbrows);
	if(ndbrows<=0)
	{
		elog_complain(1,"Working database view is empty\n");
		if(substr!=NULL) elog_complain(0,"Subset condtion =%s a likely problem\n",
				substr);
		usage();
	}

	fp = fopen(outfile,"w");
	if(fp == NULL)
	{
		elog_complain(0,"Cannot open output file %s\n",outfile);
		usage();
	}

	/* These are needed for sort below */
	pushtbl(sortkeys,"sta");
	pushtbl(sortkeys,"chan");

    /* Set up and write the Textual File Header */
	initialize_text_header(text_file_header, segy_format,
			text_header_description);

	if(write_reel_headers){
		if ( fwrite(text_file_header,1,SEGY_TEXT_HEADER_SIZE,fp) \
				!= SEGY_TEXT_HEADER_SIZE ) {
			elog_die(1,"An error occurred writing the textual file header");
		}
	}

	/* memory allocation for trace data.  This is a large matrix
	that is cleared for each event.  This model works because of
	segy's fixed length format.*/
	traces = calloc(nchan, sizeof(float*));
	if(traces == NULL)
		elog_die(1,"out of memory");
	for (int r = 0; r < nchan; r++)
	{
		traces[r] = calloc(nsamp0, sizeof(float));
		if(traces[r] == NULL)
			elog_die(1,"out of memory");
	}
	header = (SEGYTraceHeader *)calloc((size_t)nchan,sizeof(SEGYTraceHeader));
	if(header == NULL)
			elog_die(0,"Cannot alloc memory for %d segy header workspace\n",nchan);
	if(write_reel_headers)
	{
		if (Verbose) {
			elog_debug(0,"Binary Headers - Using segy_format code 0x%04X\n", ntohs(segy_format));
		}
		initialize_binary_file_header(&reel, segy_format);

		/* now fill in the binary reel header and write it */
		reel.kjob   = htonl(1);
		reel.kline  = htonl(1);
		reel.kreel  = htonl(1);
		reel.kntr   = htons((int16_t)nchan);
		reel.knaux  = htons(0);
		reel.sr     = htons((int16_t)(1000000.0/samprate0));
		reel.kfldsr = reel.sr;
		reel.knsamp = htons((int16_t)nsamp0);
		reel.kfsamp = htons((int16_t)nsamp0);
		reel.dsfc   = htons(5);  /* This is ieee floats*/
		reel.kmfold = htons(0);
		if(map_to_cdp)
			reel.ksort = htons(2);
		else
			reel.ksort = htons(1);
		reel.kunits = htons(1);  /* This sets units to always be meters */

		if(fwrite((void *)(&reel),sizeof(SEGYBinaryFileHeader),1,fp) != 1)
		{
			elog_die(1,"Write error for binary reel header");
		}
	}

	/* Now we enter a loop over stdin reading start times.
	Program will blindly ask for data from each start time to
	time+tlength.  The trace buffer will be initialized to
	zeros at the top of the loop always.  If nothing is found
	only zeros will be written to output.
	*/
	while((stest=fgets(s,80,stdin)) != NULL)
	{
		double slat,slon,selev;  /* Used when reading source location*/
		if(Verbose)
			elog_notify(0,"Processing:  %s\n",s);
		for(i=0;i<nchan;++i)
		{
			initialize_trace_header(&(header[i]), segy_format);
			header[i].gainType = trace_gain_type;
			header[i].lineSeq = htonl(total_traces + i + 1);
			header[i].reelSeq = header[i].lineSeq;
			if(map_to_cdp)
			{
				header[i].cdpEns = htonl(i + 1);
				header[i].traceInEnsemble = htonl(1);/* 1 trace per cdp faked */
			}
			else
			{
				header[i].channel_number = htonl(i + 1);
			}
			header[i].event_number   = htonl(shotid);
			header[i].energySourcePt = htonl(shotid);
			for(j=0;j<nsamp0;++j)  traces[i][j] = htonf((Trsample)0.0);
		}
		if(input_source_coordinates)
		{
			char stmp[40];
			sscanf(s,"%s%ld%lf%lf%lf",stmp,&shotid,&slon,&slat,&selev);
			time0=str2epoch(stmp);
			if(coordUnits == SEGY_TRACE_COORDUNITS_ARCSECONDS) {
				slat*=3600.0;
				slon*=3600.0;
			}
			slat *= (double)coordScale;
			slon *= (double)coordScale;
		}
		else
		{
			time0 = str2epoch(s);
		}
		endtime0 = time0 + tlength;
		sprintf(stime,"%20.4f",time0);
		sprintf(etime,"%20.4f",endtime0);
		trdb.database = -1;
		if(trload_css(dbj,stime,etime,&trdb,0, 0) < 0)
		{
			if(Verbose)
			{
			  elog_notify(0,"trload_css failed for shotid=%ld",shotid);
			  elog_notify(0,"  No data in time range %s to %s\n",
			  	strtime(time0),strtime(endtime0) );
			  elog_notify(0,"No data written for this shotid block.");
			  elog_notify(0,"  Handle this carefully in geometry definitions.\n");
			}

			continue;
		}
		/* This does gap processing */
		repair_gaps(trdb);

		trapply_calib(trdb);

		if(rotate)
		{
			if(rotate_to_standard(trdb,newchan_standard))
				elog_notify(0,"Data loss in rotate_to_standard for event %s to %s\n",
					stime, etime);
			/* This is need to prevent collisions of channel names */
			trdbss = dbsubset(trdb,trsubset,0);
			if(trrotate(trdbss,phi,theta,newchan))
				elog_notify(0,"Data loss in trrotate for event %s to %s\n",
					stime, etime);
		}
		if(Verbose)
			elog_notify(0,"Station  chan_name  chan_number seq_number shotid  evid\n");
		trdb = dbsort(trdb,sortkeys,0,0);
		dbquery(trdb,dbRECORD_COUNT,&ntraces);
		if(Verbose) elog_debug(0,"Read %ld traces for event at time%s\n",
			ntraces,strtime(time0));
		for(trdb.record=0;trdb.record<ntraces;++trdb.record)
		{
			Trsample *trdata;
			if(dbgetv(trdb,0,
			    "evid",&evid,
			    "sta",sta,
			    "chan",chan,
			    "nsamp", &nsamp,
			    "samprate",&samprate,
			    "data",&trdata,
			    "lat", &lat,
			    "lon", &lon,
			    "elev",&elev,
			    "refsta",refsta,
			    "dnorth",&dnorth,
			    "deast",&deast,
			    "edepth",&edepth,
			    "segtype",&segtype,
			    NULL) == dbINVALID)
			{
				elog_complain(0," dbgetv error reading record %ld. Trace will be skipped for station %s and channel %s",
				trdb.record,sta,chan);
				continue;
			}
			/* Allow 1 percent samprate error before killing */
			double fsrskew=fabs((samprate-samprate0)/samprate0);
			double frskewcut=0.01;
			if(fsrskew>frskewcut)
			{
				elog_complain(0,"%s:%s sample rate %f is significantly different from base sample rate of %f. Trace skipped -- segy requires fixed sample rates",
					sta,chan,samprate,samprate0);
				continue;
			}
			if(nsamp > nsamp0)
			{
				elog_complain(0,"%s:%s trace has extra samples=%ld. Truncated to length %d",
					sta, chan, nsamp, nsamp0);
				nsamp = nsamp0;
			}
			else if(nsamp < nsamp0)
			{
				elog_complain(0,"%s:%s trace is shorter than expected %d samples. Zero padded after sample %ld",
					sta, chan, nsamp0, nsamp);
			}

			ichan = get_channel_index(channels,sta,chan);
			if(ichan > nchan)
			{
				elog_die(0,"Channel index %d outside limit of %d. Cannot continue",
					ichan, nchan);
			}
			if(ichan >= 0)
			{
				if(Verbose)
					elog_debug(0,"%s:%s\t%-d\t%-d\t%-ld\t%-ld\n",
					sta,chan,ichan+1,
					ntohl(header[ichan].reelSeq),
					shotid, evid);
				header[ichan].traceID = get_trace_id_code_from_segtype(segtype);
				for(j=0;j<nsamp;++j) {
				   traces[ichan][j] = htonf((float)trdata[j]);
				}
				/* header fields coming from trace table */
				header[ichan].samp_rate = htonl(
						(int32_t) (1000000.0/samprate0));
				/* according to the behavior specified in the man page:
				 * if use_geo_coordinates is false:
				 * - coordUnits is length (meters)
				 * - therefore, we use deast for X and dnorth for Y
				 * if use_geo_coordinates is true:
				 * - we're using either arcseconds or decimal degrees
				 * - and therefore, we use lon for X and lat for Y
				 *
				 * coordUnits is based on use_arcseconds and the requested
				 * version of segY */

				/* set the coordinate units in the trace header */
				header[ichan].coordUnits = coordUnits;

				/* Pick the source db fields for our receiver X and Y */
				double recLongOrX = 0;
				double recLatOrY  = 0;
				if (coordUnits == SEGY_TRACE_COORDUNITS_LENGTH) {
					/* Use deast and dnorth
					 * CSS3.0 Schema specifies deast and dnorth are in KM.
					 * SEG-Y specifies easting and northing as meters,
					 * hence the 1000.0 multiplier here. */
					recLongOrX = deast  * 1000.0;
					recLatOrY  = dnorth * 1000.0;
				} else if (coordUnits == SEGY_TRACE_COORDUNITS_ARCSECONDS){
					/* Use lat and lon, converted to arcseconds */
					recLongOrX = lon * 3600.0;
					recLatOrY  = lat * 3600.0;
				} else {
					/* Default case, which covers decimal degrees */
					recLongOrX = lon;
					recLatOrY  = lat;
				}

				/* Apply our coordScale - the user can specify negative numbers,
				 * but they are treated as inverting the value, not as a divisor
				 * as in the SEG-Y field usage. See below where we always treat
				 * the scalar as a divisor in the SEG-Y field */
				recLongOrX *= (double)coordScale;
				recLatOrY  *= (double)coordScale;

				/* Set the coordScale in the header.
				 * Note negative here.  This is a oddity of segy that - means
				 * divide by this to get actual.  Always make this negative in
				 * case user inputs a negative number.
				 * Don't set it -1 for cosmetic reasons */
				if (abs(coordScale) == 1)
				{
					header[ichan].coordScale = htons(1);
				} else
				{
					header[ichan].coordScale = htons(-abs(coordScale));
				}

				/* Finally, write out the X and Y */
				header[ichan].recLongOrX
					= htonl((int32_t)recLongOrX);
				header[ichan].recLatOrY
					= htonl((int32_t)recLatOrY);

				/* CSS3.0 specfies elev as being in km, SEG-Y wants it in m */
				header[ichan].recElevation = htonl((int32_t)(elev*1000.0));

				header[ichan].deltaSample = htons(
						(int16_t) (1000000.0/samprate0));
				header[ichan].sampleLength = htons((int16_t)nsamp0);
				if (ntohs(segy_format)<0x0100)
				{
					header[ichan].num_samps = htonl((int32_t)nsamp0);
				}
				/* This cracks the time fields */
				time_str = epoch2str(time0,fmt);
				int16_t hyear, hday, hhour, hminute, hsecond, hm_secs;
				hyear=hday=hhour=hminute=hsecond=hm_secs=0;
				sscanf(time_str,"%hd %hd %hd %hd %hd %hd",
						&hyear, &hday, &hhour, &hminute, &hsecond, &hm_secs);
				header[ichan].year   = htons(hyear);
				header[ichan].day    = htons(hday);
				header[ichan].hour   = htons(hhour);
				header[ichan].minute = htons(hminute);
				header[ichan].second = htons(hsecond);
				header[ichan].m_secs = htons(hm_secs);
				if (ntohs(segy_format)<0x0100)
				{
					/* These are IRIS-PASSCAL extensions */
					header[ichan].trigyear   = header[ichan].year;
					header[ichan].trigday    = header[ichan].day;
					header[ichan].trighour   = header[ichan].hour;
					header[ichan].trigminute = header[ichan].minute;
					header[ichan].trigsecond = header[ichan].second;
				}
				free(time_str);
				if(input_source_coordinates)
				{
					/* Write out our pre-scaled and optionally
					 * arcsecond-converted source lat/lon plus our elevation */
					header[ichan].sourceLongOrX = htonl((int32_t)slon);
					header[ichan].sourceLatOrY  = htonl((int32_t)slat);
					header[ichan].sourceSurfaceElevation
						= htonl((int32_t)selev);
					/* No easy way to specify both elev and depth*/
					header[ichan].sourceDepth=htonl(0);
				}
				else if(map_to_cdp)
				{
				/* When faking CDP data we make this look
				like a zero offset, single fold data set */
					header[ichan].sourceLongOrX   = header[ichan].recLongOrX;
					header[ichan].sourceLatOrY    = header[ichan].recLatOrY;
					header[ichan].sourceSurfaceElevation
					                              = header[ichan].recElevation;
					header[ichan].sourceDepth     = htonl(0);
					header[ichan].sourceToRecDist = htonl(0);
				}
				else
				{
				/* This is the mechanism for adding other
				information with added tables.  The one
				table currently supported is a "shot" table
				that holds shot coordinates.  If other tables
				were added new functions could be added with
				a similar calling sequence.  This procedure
				silently does nothing if a shot table is not
				present.*/
					set_shot_variable(db,table_list,
						evid,&header[ichan]);
				}
			}
			else
			{
				if(Verbose)
					elog_notify(0,"Station %s and channel %s skipped\n",
						sta,chan);
			}

		}
		/* Now we write the data */
		for(i=0;i<nchan;++i)
		{
			if(fwrite((void *)(&(header[i])),sizeof(SEGYTraceHeader),1,fp) != 1)
				elog_die(0,"Write error on header for trace %d\n",total_traces+i);
			if(fwrite((void *)traces[i],sizeof(float),
					(size_t)nsamp0,fp) != nsamp0)
				elog_die(0,"Write error while writing data for trace %d\n",
					total_traces+i);
		}
		total_traces += nchan;
		trdestroy(&trdb);
		if(!input_source_coordinates) ++shotid;
	}
	return 0 ;
}
Пример #2
0
int main(int argc, char **argv)
{
	SegyReel reel;
	SegyHead *header;
	char *dbin;
	char *outfile;
	FILE *fp;
	Pf *pf;  
	Arr *channels;  /* channel order list */
	Arr *table_list;  /* array of valid tables */
	int nchan;
	char *stest;

	float **traces;
	char reel1[3200];
	Dbptr db, trdb, dbj;
	Dbptr trdbss;  
	int nsamp0;
	double time0, endtime0, samprate0;
	long int nsamp;
	double samprate;
	int i,j;
	char stime[30],etime[30];
	char s[128];
	double tlength;
	double phi, theta;
	char *newchan_standard[3]={"X1","X2","X3"};
	char *trsubset="chan=~/X./";
	char *newchan[3]={"R","T","Z"};
	Tbl *sortkeys=newtbl(0);
	char sta[10],chan[10];
	double lat, lon, elev, dnorth, deast, edepth;
	char refsta[10];
	int total_traces=0;
	char *time_str;
	long int evid,shotid=1;
	int rotate=0;
	long int ntraces;
        int ichan;
	int map_to_cdp;  /* logical switch to output data like cdp stacked data */
	char *fmt="%Y %j %H %M %S %s";
	char *pfname;
	int Verbose=0;
	/* New features added 2009 */
	/* this is a boolean.  If true (nonzero) it is assumed stdin will
	contain four numbers:  time,lat, lon, elev.  If false, only the
	time field is read and remainder of any input on each line is dropped.*/
	int input_source_coordinates;
	/* scale factor for source coordinates.  Needed because segy uses
	an int to store source coordinates.  Sensible choices are 
	3600 for arc seconds and 10000 for a pseudodecimal. Note this
	parameter is ignored unless input_source_coordinates is true.*/
	int coordScale;
	/* If true use passcal 32 bit extension num_samps as record length. 
	SEGY standard uses a 16 bit entry that easily overflows with large
	shots at long offset.  In this ase assume the 16 bit quantity is
	meaningless. */
	int use_32bit_nsamp;
	/* This is switched on by argument switch.  When set to a nonzero
	(default) the reel headers are written.  When 0 `
	the reel heades will not be written -- used by seismic unix 
r
	and passcal*/
	int write_reel_headers=1;
	char *substr=NULL;

	if(argc < 3) usage();
	dbin = argv[1];
	outfile = argv[2];
	pfname = NULL;
	for(i=3;i<argc;++i)
	{
		if(!strcmp(argv[i],"-pf"))
		{
			++i;
			pfname = argv[i];
		}
		else if(!strcmp(argv[i],"-SU"))
		{
			write_reel_headers=0;
		}
		else if(!strcmp(argv[i],"-v"))
		{
			Verbose=1;
		}
		else if(!strcmp(argv[i],"-ss"))
		{
			++i;
			substr=argv[i];
		}
		else
		{
			usage();
		}
	}
	if(pfname == NULL) pfname = strdup("db2segy");

	elog_init(argc, argv);

	if(pfread(pfname,&pf)) 
		elog_die(0,"pfread error for pf file %s.pf\n",argv[0]);
	/* rotation parameters */
	rotate=pfget_boolean(pf,"rotate");
	if(rotate)
	{
		phi = pfget_double(pf,"phi");
		theta = pfget_double(pf,"theta");
	}
	/* This function creates the channel order list keyed by
	station channel names */
	channels = build_stachan_list(pf,&nchan,Verbose);

	map_to_cdp = pfget_boolean(pf,"map_to_cdp");
	if(map_to_cdp && Verbose) 
		fprintf(stdout,"Casting data as CDP stacked section\n");
	if(dbopen(dbin,"r",&db) == dbINVALID) 
	{
		fprintf(stderr,"Cannot open db %s\n", dbin);
		usage();
	}
	/* We grab the sample rate and trace length (in seconds) and
	use this to define global sample rates for the data.  
	segy REQUIRES fixed length records and sample rates, so
	irregular sample rates will cause this program to die. 
	One could add a decimate/interpolate function, but this 
	is not currently implemented */
	samprate0 = pfget_double(pf,"sample_rate");
	tlength = pfget_double(pf,"trace_length");
	nsamp0 = (int)(tlength*samprate0);
	use_32bit_nsamp=pfget_boolean(pf,"use_32bit_nsamp");

	/* nsamp in segy is a 16 bit field.  Handling depends on
	setting of use_32bit_nsamp boolean */
	if(nsamp0 > 32767) 
	{
	    if(use_32bit_nsamp)
	    {
	    	elog_notify(0,"Warning:  segy ues a 16 bit entity to store number of samples\nThat field is garbage. Using the 32 bit extension field.\n");
	    }
	    else
	    {
		elog_complain(0,
		  "Warning:  segy uses a 16 bit entity to store number of samples\nRequested %d samples per trace.  Trucated to 32767\n",nsamp0);
		nsamp0 = 32767;
	    }
	}
	input_source_coordinates=pfget_boolean(pf,"input_source_coordinates");
	if(input_source_coordinates)
	{
		coordScale=pfget_int(pf,"coordinate_scale_factor");
	}
	else
	{
		coordScale=1;
	}
	/* boolean.  When nonzero set coordinates as geographic arc seconds values */
	int use_geo_coordinates=pfget_boolean(pf,"use_geo_coordinates");
	/* check list of tables defined in pf.  Return array of
	logicals that define which tables are valid and join 
	tables. */
	table_list = check_tables(db,pf);
	check_for_required_tables(table_list);
	dbj = join_tables(db,pf,table_list);
	if(dbj.record == dbINVALID) elog_die(0,"dbjoin error\n");
	if(substr!=NULL) dbj=dbsubset(dbj,substr,0);
	long int ndbrows;
	dbquery(dbj,dbRECORD_COUNT,&ndbrows);
	if(ndbrows<=0)
	{
		fprintf(stderr,"Working database view is empty\n");
		if(substr!=NULL) fprintf(stderr,"Subset condtion =%s a likely problem\n",
				substr);
		usage();
	}

	fp = fopen(outfile,"w");
	if(fp == NULL) 
	{
		fprintf(stderr,"Cannot open output file %s\n",outfile);
		usage();
	}

	/* These are needed for sort below */
	pushtbl(sortkeys,"sta");
	pushtbl(sortkeys,"chan");

	/*The reel1 header in true blue segy is ebcdic.  We are goingto 
	just fill it with nulls and hope for the best */
	for(i=0;i<3200;i++) reel1[i] = '\0';

	/* Just blindly write this turkey. Bad form, but tough*/
	if(write_reel_headers) fwrite(reel1,1,3200,fp);

	/* memory allocation for trace data.  This is a large matrix
	that is cleared for each event.  This model works because of
	segy's fixed length format.  This routine is a descendent of
	numerical recipes routine found in libgenloc.  This is not
	the most efficient way to do this, but it simplifies the
	algorithm a lot. */
	traces = matrix(0,nchan,0,nsamp0);
	if(traces == NULL) 
		elog_die(0,"Cannot alloc trace data matrix work space of size %d by %d\n",
			nchan, nsamp0);
	header = (SegyHead *)calloc((size_t)nchan,sizeof(SegyHead));
	if(header == NULL)
			elog_die(0,"Cannot alloc memory for %d segy header workspace\n",nchan);
	if(write_reel_headers)
	{

		/* now fill in the binary reel header and write it */
		reel.kjob = 1;
		reel.kline = 1;
		reel.kreel = 1;
		reel.kntr = (int16_t)nchan;
		reel.knaux = 0;
		reel.sr = (int16_t)(1000000.0/samprate0);
		reel.kfldsr = reel.sr;
		reel.knsamp = (int16_t)nsamp0;
		reel.kfsamp = (int16_t)nsamp0;
		reel.dsfc=5;  /* This is ieee floats*/
		reel.kmfold = 0;
		if(map_to_cdp)
			reel.ksort = 2;
		else
			reel.ksort = 1;
		reel.kunits = 1;  /* This sets units to always be meters */
		for(i=0;i<344;++i)reel.unused2[i]='\0';
	
		if(fwrite((void *)(&reel),sizeof(SegyReel),1,fp) != 1) 
		{
			fprintf(stderr,"Write error for binary reel header\n");
			exit(-2);
		}
	}

	/* Now we enter a loop over stdin reading start times.  
	Program will blindly ask for data from each start time to 
	time+tlength.  The trace buffer will be initialized to 
	zeros at the top of the loop always.  If nothing is found
	only zeros will be written to output.  
	*/
	while((stest=fgets(s,80,stdin)) != NULL)
	{
		double slat,slon,selev;  /* Used when reading source location*/
		if(Verbose)
			fprintf(stdout,"Processing:  %s\n",s);
		for(i=0;i<nchan;++i)
		{
			initialize_header(&(header[i]));
			header[i].lineSeq = total_traces + i + 1;
			header[i].reelSeq = header[i].lineSeq;
			if(map_to_cdp)
			{
				header[i].cdpEns = i + 1;
				header[i].traceInEnsemble = 1;  /* 1 trace per cdp faked */
			}
			else
			{
				header[i].channel_number = i + 1;
			}
			header[i].event_number = shotid;
			header[i].energySourcePt=shotid;
			for(j=0;j<nsamp0;++j)  traces[i][j] = (Trsample)0.0;
		}
		if(input_source_coordinates)
		{
			char stmp[40];
			sscanf(s,"%s%ld%lf%lf%lf",stmp,&shotid,&slon,&slat,&selev);
			time0=str2epoch(stmp);
		}
		else
		{
			time0 = str2epoch(s);
		}
		endtime0 = time0 + tlength;
		sprintf(stime,"%20.4f",time0);
		sprintf(etime,"%20.4f",endtime0);
		trdb.database = -1;
		if(trload_css(dbj,stime,etime,&trdb,0, 0) < 0)
		{
			if(Verbose) 
			{
			  fprintf(stdout,"trload_css failed for shotid=%ld",shotid);
			  fprintf(stdout,"  No data in time range %s to %s\n",
			  	strtime(time0),strtime(endtime0) );
			  fprintf(stdout,"No data written for this shotid block.");
			  fprintf(stdout,"  Handle this carefully in geometry definitions.\n");
			}

			continue;
		}
		/* This does gap processing */
		repair_gaps(trdb);
		
		trapply_calib(trdb);
			
		if(rotate)
		{
			if(rotate_to_standard(trdb,newchan_standard))
				elog_notify(0,"Data loss in rotate_to_standard for event %s to %s\n",
					stime, etime);
			/* This is need to prevent collisions of channel 
			names */
			trdbss = dbsubset(trdb,trsubset,0);
			if(trrotate(trdbss,phi,theta,newchan))
				elog_notify(0,"Data loss in trrotate for event %s to %s\n",
					stime, etime);
		}
		if(Verbose)
			fprintf(stdout,"Station  chan_name  chan_number seq_number shotid  evid\n");
		trdb = dbsort(trdb,sortkeys,0,0);
		dbquery(trdb,dbRECORD_COUNT,&ntraces);
		if(Verbose) fprintf(stdout,"Read %ld traces for event at time%s\n",
			ntraces,strtime(time0));
		for(trdb.record=0;trdb.record<ntraces;++trdb.record)
		{
			Trsample *trdata;
			if(dbgetv(trdb,0,
			    "evid",&evid,
			    "sta",sta,
			    "chan",chan,
			    "nsamp", &nsamp,
			    "samprate",&samprate,
			    "data",&trdata,
			    "lat", &lat,
			    "lon", &lon,
			    "elev",&elev,
			    "refsta",refsta,
			    "dnorth",&dnorth,
			    "deast",&deast,
			    "edepth",&edepth,
					NULL) == dbINVALID)
			{
				elog_complain(0," dbgetv error reading record %ld\nTrace will be skipped for station %s and channel %s\n",
				trdb.record,sta,chan);
				continue;
			}
			/* Allow 1 percent samprate error before killing */
			double fsrskew=fabs((samprate-samprate0)/samprate0);
			double frskewcut=0.01;
			if(fsrskew>frskewcut) 
			{
				elog_complain(0,"%s:%s sample rate %f is significantly different from base sample rate of %f\nTrace skipped -- segy requires fixed sample rates\n",
					sta,chan,samprate,samprate0);
				continue;
			}
			if(nsamp > nsamp0)
			{
				elog_complain(0,"%s:%s trace has extra samples=%ld\nTruncated to length %d\n",
					sta, chan, nsamp, nsamp0);
				nsamp = nsamp0;
			}
			else if(nsamp < nsamp0)
			{
				elog_complain(0,"%s:%s trace is shorter than expected %d samples\nZero padded after sample %ld\n",
					sta, chan, nsamp0, nsamp);
			}

			ichan = get_channel_index(channels,sta,chan);
			if(ichan > nchan) elog_die(0,"Channel index %d outside limit of %d\nCannot continue\n",
					ichan, nchan);
			if(ichan >= 0)
			{
				if(Verbose) 
				   fprintf(stdout,"%s:%s\t%-d\t%-d\t%-ld\t%-ld\n",
					sta,chan,ichan+1,
                                        header[ichan].reelSeq,
					shotid, evid);
				header[ichan].traceID = 1;
				for(j=0;j<nsamp;++j) 
				   traces[ichan][j] = (float)trdata[j];
				/* header fields coming from trace table */
				header[ichan].samp_rate = (int32_t)
						(1000000.0/samprate0);
				if(!use_geo_coordinates && ( coordScale==1))
				{
				  header[ichan].recLongOrX = (int32_t)(deast*1000.0);
				  header[ichan].recLatOrY = (int32_t)(dnorth*1000.0);
				}
				else
				{
				/* Note negative here.  This is a oddity
				of segy that - means divide by this to
				get actual.  Always make this negative in case 
				user inputs a negative number. */
				  header[ichan].coordScale=-abs(coordScale);
				  /* Force 2 = geographic coordinates.  Standard says when this is
				  so units are arc seconds, hence we multiply deg by 3600*coordScale */
				  if(use_geo_coordinates)
				  {
				    header[ichan].coordUnits=2;
				    header[ichan].recLongOrX
				     =(int32_t)(lon*3600.0*(double)coordScale);
				    header[ichan].recLatOrY
				     =(int32_t)(lat*3600.0*(double)coordScale);
				  }
				  else
				  {
				    header[ichan].recLongOrX
				     =(int32_t)(lon*(double)coordScale);
				    header[ichan].recLatOrY
				     =(int32_t)(lat*(double)coordScale);
				  }
				}
				header[ichan].recElevation = (int32_t)(elev*1000.0);
				header[ichan].deltaSample = (int16_t) 
						(1000000.0/samprate0);
				header[ichan].sampleLength = (int16_t)nsamp0;
				header[ichan].num_samps = (int32_t)nsamp0;
				/* This cracks the time fields */
				time_str = epoch2str(time0,fmt);
				sscanf(time_str,"%hd %hd %hd %hd %hd %hd",
					&header[ichan].year,
					&header[ichan].day,
					&header[ichan].hour,
					&header[ichan].minute,
					&header[ichan].second,
					&header[ichan].m_secs);
				/* These are PASSCAL extensions, but we'll
				go ahead and set them anyway.*/
				header[ichan].trigyear = header[ichan].year;
				header[ichan].trigday = header[ichan].day;
				header[ichan].trighour = header[ichan].hour;
				header[ichan].trigminute = header[ichan].minute;
				header[ichan].trigsecond = header[ichan].second;
				free(time_str);
				if(input_source_coordinates)
				{
				  if(use_geo_coordinates)
				  {
					slat*=3600.0;
					slon*=3600.0;
				  }
				  header[ichan].sourceLongOrX
				    =(int32_t)(slon*(double)coordScale);
				  header[ichan].sourceLatOrY
				    =(int32_t)(slat*(double)coordScale);
				  header[ichan].sourceSurfaceElevation
				             =(int32_t)selev;
				  /* No easy way to specify both elev and depth*/
				  header[ichan].sourceDepth=0;
				}
				else if(map_to_cdp)
				{
				/* When faking CDP data we make this look 
				like a zero offset, single fold data set */
				  header[ichan].sourceLongOrX = header[ichan].recLongOrX;
				  header[ichan].sourceLatOrY = header[ichan].recLatOrY;
				  header[ichan].sourceSurfaceElevation = header[ichan].recElevation;
				  header[ichan].sourceDepth = 0;
				  header[ichan].sourceToRecDist = 0;
				}
				else
				{
				/* This is the mechanism for adding other
				information with added tables.  The one
				table currently supported is a "shot" table 
				that holds shot coordinates.  If other tables
				were added new functions could be added with
				a similar calling sequence.  This procedure
				silently does nothing if a shot table is not
				present.*/
					set_shot_variable(db,table_list,
						evid,&header[ichan]);
				}
			}			
			else
			{
				if(Verbose)
					fprintf(stdout,"Station %s and channel %s skipped\n",
						sta,chan);
			}

		}
		/* Now we write the data */
		for(i=0;i<nchan;++i)
		{
			if(fwrite((void *)(&(header[i])),sizeof(SegyHead),1,fp) != 1)
				elog_die(0,"Write error on header for trace %d\n",total_traces+i);		
			if(fwrite((void *)traces[i],sizeof(float),
					(size_t)nsamp0,fp) != nsamp0)
				elog_die(0,"Write error while writing data for trace %d\n",
					total_traces+i);
		}
		total_traces += nchan;
		trdestroy(&trdb);		
		if(!input_source_coordinates) ++shotid;
	}
	return 0 ;
}