diff --git a/cmd/fdsn-ws/fdsn_event.go b/cmd/fdsn-ws/fdsn_event.go index 8845578..175ec03 100644 --- a/cmd/fdsn-ws/fdsn_event.go +++ b/cmd/fdsn-ws/fdsn_event.go @@ -572,7 +572,7 @@ func fdsnEventV1Handler(r *http.Request, h http.Header, b *bytes.Buffer) error { if l, err := wgs84.ClosestNZ(latitude, longitude); err == nil { loc = l.Description() } - s := fmt.Sprintf("%s|%s|%.3f|%.3f|%.1f|GNS|GNS|GNS|%s|%s|%.1f|GNS|%s|%s\n", eventID, tm.Format("2006-01-02T15:04:05"), latitude, longitude, depth, eventID, magType, magnitude, loc, eventType) + s := fmt.Sprintf("%s|%s|%.3f|%.3f|%.1f|GNS|GNS|GNS|%s|%s|%.1f|GNS|%s|%s\n", eventID, tm.UTC().Format(time.RFC3339Nano), latitude, longitude, depth, eventID, magType, magnitude, loc, eventType) b.WriteString(s) } diff --git a/cmd/fdsn-ws/fdsn_event_test.go b/cmd/fdsn-ws/fdsn_event_test.go index 6a7b6b1..4f43e62 100644 --- a/cmd/fdsn-ws/fdsn_event_test.go +++ b/cmd/fdsn-ws/fdsn_event_test.go @@ -118,7 +118,7 @@ func TestTimeParse(t *testing.T) { t.Error(err) } - if err := tm.UnmarshalText([]byte("2015-01-12T12:12:12-09:00")); err == nil { + if err := tm.UnmarshalText([]byte("2015-01-12T12:12:12.invalid")); err == nil { t.Error("expected an error for invalid time string.") } } diff --git a/cmd/fdsn-ws/fdsn_station.go b/cmd/fdsn-ws/fdsn_station.go index 79dc83d..ba14dbc 100644 --- a/cmd/fdsn-ws/fdsn_station.go +++ b/cmd/fdsn-ws/fdsn_station.go @@ -108,8 +108,8 @@ var ( fdsnStations fdsnStationObj emptyDateTime = time.Date(9999, 1, 1, 0, 0, 0, 0, time.UTC) errNotModified = fmt.Errorf("Not modified.") - s3Bucket string - s3Meta string + stationXMLBucket string + stationXMLKey string ) func initStationTemplate() { @@ -134,18 +134,18 @@ func initStationTemplate() { func initStationXML() { var err error - s3Bucket = os.Getenv("STATION_XML_BUCKET") - s3Meta = os.Getenv("STATION_XML_META_KEY") + stationXMLBucket = os.Getenv("STATION_XML_BUCKET") + stationXMLKey = os.Getenv("STATION_XML_META_KEY") // Prepare the data source for station. // If there's no local file available then we'll have to download first. by := bytes.NewBuffer(nil) modified := zeroDateTime var s os.FileInfo - if s, err = os.Stat("etc/" + s3Meta); err == nil { - log.Println("Loading fdsn station xml file ", "etc/"+s3Meta) + if s, err = os.Stat("etc/" + stationXMLKey); err == nil { + log.Println("Loading fdsn station xml file ", "etc/"+stationXMLKey) var f *os.File - if f, err = os.Open("etc/" + s3Meta); err == nil { + if f, err = os.Open("etc/" + stationXMLKey); err == nil { if _, err = io.Copy(by, f); err != nil { log.Println("Error copying station xml file", err) @@ -656,7 +656,7 @@ func (r *FDSNStationXML) doFilter(params []fdsnStationV1Search) bool { // If this node meets at least one criteria, then we pass all the met criterion to next level. func (n *NetworkType) doFilter(params []fdsnStationV1Search) bool { - n.TotalNumberStations = len(n.Station) + n.TotalNumberStations = CounterType(len(n.Station)) matchedParams := make([]fdsnStationV1Search, 0) resultStations := make([]StationType, 0) @@ -699,7 +699,7 @@ func (n *NetworkType) doFilter(params []fdsnStationV1Search) bool { } } - n.SelectedNumberStations = len(resultStations) + n.SelectedNumberStations = CounterType(len(resultStations)) n.Station = resultStations // this node only valid if the children matches any query @@ -707,7 +707,7 @@ func (n *NetworkType) doFilter(params []fdsnStationV1Search) bool { } func (s *StationType) doFilter(params []fdsnStationV1Search) bool { - s.TotalNumberChannels = len(s.Channel) + s.TotalNumberChannels = CounterType(len(s.Channel)) resultChannels := make([]ChannelType, 0) matchedParams := make([]fdsnStationV1Search, 0) @@ -835,39 +835,31 @@ func (v fdsnStationV1Search) validStartEnd(start, end time.Time, level int) bool return true } -func (v fdsnStationV1Search) validLatLng(latitude *LatitudeType, longitude *LongitudeType) bool { - if v.MinLatitude != math.MaxFloat64 && (latitude == nil || latitude.Value < v.MinLatitude) { - // request to check latitude: - // 1. this node doesn't have latitude -> check failed - // 2. the value fall outside the range -> check failed - // (similar logics apply for cases below) +func (v fdsnStationV1Search) validLatLng(latitude LatitudeType, longitude LongitudeType) bool { + if v.MinLatitude != math.MaxFloat64 && latitude.Value < v.MinLatitude { return false } - if v.MaxLatitude != math.MaxFloat64 && (latitude == nil || latitude.Value > v.MaxLatitude) { + if v.MaxLatitude != math.MaxFloat64 && latitude.Value > v.MaxLatitude { return false } - if v.MinLongitude != math.MaxFloat64 && (longitude == nil || longitude.Value < v.MinLongitude) { + if v.MinLongitude != math.MaxFloat64 && longitude.Value < v.MinLongitude { return false } - if v.MaxLongitude != math.MaxFloat64 && (longitude == nil || longitude.Value > v.MaxLongitude) { + if v.MaxLongitude != math.MaxFloat64 && longitude.Value > v.MaxLongitude { return false } return true } -func (v fdsnStationV1Search) validBounding(latitude *LatitudeType, longitude *LongitudeType) bool { +func (v fdsnStationV1Search) validBounding(latitude LatitudeType, longitude LongitudeType) bool { if v.Latitude == math.MaxFloat64 { // not using bounding circle return true } - if latitude == nil || longitude == nil { - // requested bounding circle, but this node doesn't have lat/lon - return false - } d, _, err := wgs84.DistanceBearing(v.Latitude, v.Longitude, latitude.Value, longitude.Value) if err != nil { log.Printf("Error checking bounding:%s\n", err.Error()) @@ -887,28 +879,43 @@ func (v fdsnStationV1Search) validBounding(latitude *LatitudeType, longitude *Lo // Download station XML from S3 func downloadStationXML(since time.Time) (by *bytes.Buffer, modified time.Time, err error) { - s3Client, err := s3.NewWithMaxRetries(100) - if err != nil { - return - } + var tp time.Time + by = bytes.NewBuffer(nil) - tp, err := s3Client.LastModified(s3Bucket, s3Meta, "") - if err != nil { - return - } + if stationXMLBucket != "" { + var s3Client s3.S3 + s3Client, err = s3.NewWithMaxRetries(100) + if err != nil { + return + } - if !tp.After(since) { - return nil, zeroDateTime, errNotModified - } + tp, err = s3Client.LastModified(stationXMLBucket, stationXMLKey, "") + if err != nil { + return + } - log.Println("Downloading fdsn station xml file from S3: ", s3Bucket+"/"+s3Meta) + if !tp.After(since) { + return nil, zeroDateTime, errNotModified + } - by = bytes.NewBuffer(nil) - err = s3Client.Get(s3Bucket, s3Meta, "", by) - if err != nil { - return - } + log.Println("Downloading fdsn station xml file from S3: ", stationXMLBucket+"/"+stationXMLKey) + err = s3Client.Get(stationXMLBucket, stationXMLKey, "", by) + if err != nil { + return + } + } else { + // load from local to make debugging easier. + // s3Meta be the path to station xml + var f *os.File + f, err = os.Open(stationXMLKey) + if err != nil { + return + } + defer f.Close() + _, err = by.ReadFrom(f) + tp = time.Now() + } modified = tp log.Println("Download complete.") return @@ -977,7 +984,7 @@ func levelValue(level string) (int, error) { case "response": return STATION_LEVEL_RESPONSE, nil default: - return -1, fmt.Errorf("Invalid level value.") + return -1, fmt.Errorf("invalid level value") } } @@ -1020,14 +1027,13 @@ func (d xsdDateTime) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { return xml.Attr{Name: name, Value: string(t)}, nil } -// For format=text +// For format=text - now outputs RFC3339Nano with Z timezone func (d xsdDateTime) MarshalFormatText() string { if time.Time(d).Equal(zeroDateTime) || time.Time(d).Equal(emptyDateTime) { return "" } - b, _ := d.MarshalText() - return string(b) + return time.Time(d).UTC().Format(time.RFC3339Nano) } func contains(slice []string, value string) bool { diff --git a/cmd/fdsn-ws/fdsn_station_test.go b/cmd/fdsn-ws/fdsn_station_test.go index 1c91eed..52dcb1f 100644 --- a/cmd/fdsn-ws/fdsn_station_test.go +++ b/cmd/fdsn-ws/fdsn_station_test.go @@ -356,6 +356,9 @@ func TestStartEnd(t *testing.T) { } func TestFormatText(t *testing.T) { + setup(t) + defer teardown() + var e fdsnStationV1Search var err error var v url.Values = make(map[string][]string) @@ -373,7 +376,7 @@ func TestFormatText(t *testing.T) { c.doFilter([]fdsnStationV1Search{e}) b := c.marshalText(STATION_LEVEL_CHANNEL) exp := `#Network | Station | Location | Channel | Latitude | Longitude | Elevation | Depth | Azimuth | Dip | SensorDescription | Scale | ScaleFreq | ScaleUnits | SampleRate | StartTime | EndTime -NZ|ARAZ|10|EHZ|-38.627690|176.120060|420.000000|0.000000|0.000000|-90.000000|Short Period Seismometer|74574725.120000|15.000000|m/s|100.000000|2011-06-20T04:00:01| +NZ|ARAZ|10|EHZ|-38.627690|176.120060|420.000000|0.000000|0.000000|-90.000000|Short Period Seismometer|74574725.120000|15.000000|m/s|100.000000|2011-06-20T04:00:01Z| ` if b.String() != exp { t.Errorf("Incorrect text result.") @@ -388,7 +391,7 @@ NZ|ARAZ|10|EHZ|-38.627690|176.120060|420.000000|0.000000|0.000000|-90.000000|Sho c.doFilter([]fdsnStationV1Search{e}) b = c.marshalText(STATION_LEVEL_NETWORK) exp = `#Network | Description | StartTime | EndTime | TotalStations -NZ|New Zealand National Seismograph Network|1884-02-01T00:00:00||2 +NZ|New Zealand National Seismograph Network|1884-02-01T00:00:00Z||2 ` if b.String() != exp { t.Errorf("Incorrect text result.") @@ -403,7 +406,7 @@ NZ|New Zealand National Seismograph Network|1884-02-01T00:00:00||2 c.doFilter([]fdsnStationV1Search{e}) b = c.marshalText(STATION_LEVEL_STATION) exp = `#Network | Station | Latitude | Longitude | Elevation | SiteName | StartTime | EndTime -NZ|ARAZ|-38.627690|176.120060|420.000000|Aratiatia Landcorp Farm|2007-05-20T23:00:00| +NZ|ARAZ|-38.627690|176.120060|420.000000|Aratiatia Landcorp Farm|2007-05-20T23:00:00Z| ` if b.String() != exp { t.Errorf("Incorrect text result.") @@ -457,8 +460,8 @@ NZ ARA* * EHE* 2001-01-01T00:00:00 * NZ ARH? * EHN* 2001-01-01T00:00:00 *` expected := strings.TrimSpace(` #Network | Station | Latitude | Longitude | Elevation | SiteName | StartTime | EndTime -NZ|ARAZ|-38.627690|176.120060|420.000000|Aratiatia Landcorp Farm|2007-05-20T23:00:00| -NZ|ARHZ|-39.263100|176.995900|270.000000|Aropaoanui|2010-03-11T00:00:00|`) +NZ|ARAZ|-38.627690|176.120060|420.000000|Aratiatia Landcorp Farm|2007-05-20T23:00:00Z| +NZ|ARHZ|-39.263100|176.995900|270.000000|Aropaoanui|2010-03-11T00:00:00Z|`) route := wt.Request{ID: wt.L(), URL: "/fdsnws/station/1/query", Method: "POST", PostBody: []byte(body), Content: "text/plain"} diff --git a/cmd/fdsn-ws/fdsn_station_type.go b/cmd/fdsn-ws/fdsn_station_type.go index 651f77e..b405934 100644 --- a/cmd/fdsn-ws/fdsn_station_type.go +++ b/cmd/fdsn-ws/fdsn_station_type.go @@ -7,22 +7,64 @@ import ( "github.com/GeoNet/fdsn/internal/fdsn" ) -type AngleType struct { - Value float64 `xml:",chardata"` - Unit string `xml:"unit,attr,omitempty"` - PlusError *float64 `xml:"plusError,attr,omitempty"` - MinusError *float64 `xml:"minusError,attr,omitempty"` -} - // May be one of MACLAURIN type ApproximationType string -// Instrument azimuth, degrees clockwise from North. +// CounterType represents integers greater than or equal to 0 +type CounterType int + +// DistanceType represents distance measurements in meters +type DistanceType struct { + Value float64 `xml:",chardata"` + Unit string `xml:"unit,attr,omitempty"` + PlusError *float64 `xml:"plusError,attr,omitempty"` + MinusError *float64 `xml:"minusError,attr,omitempty"` + MeasurementMethod string `xml:"measurementMethod,attr,omitempty"` +} + +// AzimuthType represents azimuth in degrees clockwise from north (0-360) type AzimuthType struct { - Value float64 `xml:",chardata"` - Unit string `xml:"unit,attr,omitempty"` - PlusError *float64 `xml:"plusError,attr,omitempty"` - MinusError *float64 `xml:"minusError,attr,omitempty"` + Value float64 `xml:",chardata"` + Unit string `xml:"unit,attr,omitempty"` + PlusError *float64 `xml:"plusError,attr,omitempty"` + MinusError *float64 `xml:"minusError,attr,omitempty"` + MeasurementMethod string `xml:"measurementMethod,attr,omitempty"` +} + +// DipType represents dip in degrees, positive down from horizontal (-90 to +90) +type DipType struct { + Value float64 `xml:",chardata"` + Unit string `xml:"unit,attr,omitempty"` + PlusError *float64 `xml:"plusError,attr,omitempty"` + MinusError *float64 `xml:"minusError,attr,omitempty"` + MeasurementMethod string `xml:"measurementMethod,attr,omitempty"` +} + +// AngleType represents angle measurements in degrees (-360 to +360) +type AngleType struct { + Value float64 `xml:",chardata"` + Unit string `xml:"unit,attr,omitempty"` + PlusError *float64 `xml:"plusError,attr,omitempty"` + MinusError *float64 `xml:"minusError,attr,omitempty"` + MeasurementMethod string `xml:"measurementMethod,attr,omitempty"` +} + +// FrequencyType represents frequency measurements in Hertz +type FrequencyType struct { + Value float64 `xml:",chardata"` + Unit string `xml:"unit,attr,omitempty"` + PlusError *float64 `xml:"plusError,attr,omitempty"` + MinusError *float64 `xml:"minusError,attr,omitempty"` + MeasurementMethod string `xml:"measurementMethod,attr,omitempty"` +} + +// SampleRateType represents sample rate in samples per second +type SampleRateType struct { + Value float64 `xml:",chardata"` + Unit string `xml:"unit,attr,omitempty"` + PlusError *float64 `xml:"plusError,attr,omitempty"` + MinusError *float64 `xml:"minusError,attr,omitempty"` + MeasurementMethod string `xml:"measurementMethod,attr,omitempty"` } type BaseFilterType struct { @@ -35,15 +77,40 @@ type BaseFilterType struct { } type BaseNodeType struct { - Code string `xml:"code,attr,omitempty"` + Code string `xml:"code,attr"` StartDate xsdDateTime `xml:"startDate,attr,omitempty"` EndDate xsdDateTime `xml:"endDate,attr,omitempty"` RestrictedStatus *RestrictedStatusType `xml:"restrictedStatus,attr,omitempty"` AlternateCode string `xml:"alternateCode,attr,omitempty"` HistoricalCode string `xml:"historicalCode,attr,omitempty"` + SourceID string `xml:"sourceID,attr,omitempty"` Items []string `xml:",any,omitempty"` Description string `xml:"Description,omitempty"` Comment []CommentType `xml:"Comment,omitempty"` + Identifier []IdentifierType `xml:"Identifier,omitempty"` + DataAvailability *DataAvailabilityType `xml:"DataAvailability,omitempty"` +} + +type IdentifierType struct { + Type string `xml:"type,attr,omitempty"` + Value string `xml:",chardata"` +} + +type DataAvailabilityType struct { + Extent *DataAvailabilityExtentType `xml:"Extent,omitempty"` + Span []DataAvailabilitySpanType `xml:"Span,omitempty"` +} + +type DataAvailabilityExtentType struct { + Start xsdDateTime `xml:"start,attr"` + End xsdDateTime `xml:"end,attr"` +} + +type DataAvailabilitySpanType struct { + Start xsdDateTime `xml:"start,attr"` + End xsdDateTime `xml:"end,attr"` + NumberSegments int `xml:"numberSegments,attr"` + MaximumTimeTear float64 `xml:"maximumTimeTear,attr,omitempty"` } // May be one of ANALOG (RADIANS/SECOND), ANALOG (HERTZ), DIGITAL @@ -53,35 +120,27 @@ type CfTransferFunctionType string // response blockettes. type ChannelType struct { BaseNodeType - Unit string `xml:"unit,attr,omitempty"` - LocationCode string `xml:"locationCode,attr,omitempty"` + LocationCode string `xml:"locationCode,attr"` ExternalReference []ExternalReferenceType `xml:"ExternalReference,omitempty"` - Latitude *LatitudeType `xml:"Latitude,omitempty"` - Longitude *LongitudeType `xml:"Longitude,omitempty"` - Elevation *DistanceType `xml:"Elevation,omitempty"` - Depth *DistanceType `xml:"Depth,omitempty"` + Latitude LatitudeType `xml:"Latitude"` + Longitude LongitudeType `xml:"Longitude"` + Elevation DistanceType `xml:"Elevation"` + Depth DistanceType `xml:"Depth"` Azimuth *AzimuthType `xml:"Azimuth,omitempty"` Dip *DipType `xml:"Dip,omitempty"` + WaterLevel *FloatType `xml:"WaterLevel,omitempty"` Type []Type `xml:"Type,omitempty"` SampleRate *SampleRateType `xml:"SampleRate,omitempty"` SampleRateRatio *SampleRateRatioType `xml:"SampleRateRatio,omitempty"` - StorageFormat string `xml:"StorageFormat,omitempty"` - ClockDrift *ClockDrift `xml:"ClockDrift,omitempty"` + ClockDrift *FloatType `xml:"ClockDrift,omitempty"` CalibrationUnits *UnitsType `xml:"CalibrationUnits,omitempty"` Sensor *EquipmentType `xml:"Sensor,omitempty"` PreAmplifier *EquipmentType `xml:"PreAmplifier,omitempty"` DataLogger *EquipmentType `xml:"DataLogger,omitempty"` - Equipment *EquipmentType `xml:"Equipment,omitempty"` + Equipment []EquipmentType `xml:"Equipment,omitempty"` Response *ResponseType `xml:"Response,omitempty"` } -type ClockDrift struct { - Value float64 `xml:",chardata"` - Unit string `xml:"unit,attr,omitempty"` - PlusError *float64 `xml:"plusError,attr,omitempty"` - MinusError *float64 `xml:"minusError,attr,omitempty"` -} - type Coefficient struct { Value float64 `xml:",chardata"` FloatNoUnitType @@ -99,39 +158,23 @@ type CoefficientsType struct { } type CommentType struct { + Id *int `xml:"id,attr,omitempty"` Value string `xml:"Value,omitempty"` BeginEffectiveTime xsdDateTime `xml:"BeginEffectiveTime,omitempty"` EndEffectiveTime xsdDateTime `xml:"EndEffectiveTime,omitempty"` Author []PersonType `xml:"Author,omitempty"` + Subject string `xml:"subject,attr,omitempty"` } type DecimationType struct { - InputSampleRate *FrequencyType `xml:"InputSampleRate,omitempty"` - Factor *int `xml:"Factor,omitempty"` - Offset *int `xml:"Offset,omitempty"` - Delay *FloatType `xml:"Delay,omitempty"` - Correction *FloatType `xml:"Correction,omitempty"` + InputSampleRate FrequencyType `xml:"InputSampleRate"` + Factor int `xml:"Factor"` + Offset int `xml:"Offset"` + Delay FloatType `xml:"Delay"` + Correction FloatType `xml:"Correction"` } -// Instrument dip in degrees down from horizontal. Together azimuth and -// dip describe the direction of the sensitive axis of the instrument. -type DipType struct { - Value float64 `xml:",chardata"` - Unit string `xml:"unit,attr,omitempty"` - PlusError *float64 `xml:"plusError,attr,omitempty"` - MinusError *float64 `xml:"minusError,attr,omitempty"` -} - -// Extension of FloatType for distances, elevations, and -// depths. -type DistanceType struct { - Value float64 `xml:",chardata"` - Unit string `xml:"unit,attr,omitempty"` - PlusError *float64 `xml:"plusError,attr,omitempty"` - MinusError *float64 `xml:"minusError,attr,omitempty"` -} - -// Must match the pattern [\w\.\-_]+@[\w\.\-_]+ +// EmailType must match pattern [\w\.\-_]+@[\w\.\-_]+ type EmailType string type EquipmentType struct { @@ -154,14 +197,14 @@ type ExternalReferenceType struct { } type FDSNStationXML struct { - SchemaVersion *float64 `xml:"schemaVersion,attr,omitempty"` + SchemaVersion float64 `xml:"schemaVersion,attr"` Items []string `xml:",any,omitempty"` - Source string `xml:"Source,omitempty"` + Source string `xml:"Source"` Sender string `xml:"Sender,omitempty"` Module string `xml:"Module,omitempty"` ModuleURI string `xml:"ModuleURI,omitempty"` - Created xsdDateTime `xml:"Created,omitempty"` - Network []NetworkType `xml:"Network,omitempty"` + Created xsdDateTime `xml:"Created"` + Network []NetworkType `xml:"Network"` Xmlns string `xml:"xmlns,attr,omitempty"` } @@ -175,25 +218,20 @@ type FIRType struct { } type FloatNoUnitType struct { - Value float64 `xml:",chardata"` - PlusError *float64 `xml:"plusError,attr,omitempty"` - MinusError *float64 `xml:"minusError,attr,omitempty"` + Value float64 `xml:",chardata"` + PlusError *float64 `xml:"plusError,attr,omitempty"` + MinusError *float64 `xml:"minusError,attr,omitempty"` + MeasurementMethod string `xml:"measurementMethod,attr,omitempty"` } // Representation of floating-point numbers used as // measurements. type FloatType struct { - Value float64 `xml:",chardata"` - Unit string `xml:"unit,attr,omitempty"` - PlusError *float64 `xml:"plusError,attr,omitempty"` - MinusError *float64 `xml:"minusError,attr,omitempty"` -} - -type FrequencyType struct { - Value float64 `xml:",chardata"` - Unit string `xml:"unit,attr,omitempty"` - PlusError *float64 `xml:"plusError,attr,omitempty"` - MinusError *float64 `xml:"minusError,attr,omitempty"` + Value float64 `xml:",chardata"` + Unit string `xml:"unit,attr,omitempty"` + PlusError *float64 `xml:"plusError,attr,omitempty"` + MinusError *float64 `xml:"minusError,attr,omitempty"` + MeasurementMethod string `xml:"measurementMethod,attr,omitempty"` } type GainType struct { @@ -204,17 +242,11 @@ type GainType struct { // Base latitude type. Because of the limitations of schema, defining // this type and then extending it to create the real latitude type is the only way to // restrict values while adding datum as an attribute. -type LatitudeBaseType struct { - Value float64 `xml:",chardata"` - Unit string `xml:"unit,attr,omitempty"` - PlusError *float64 `xml:"plusError,attr,omitempty"` - MinusError *float64 `xml:"minusError,attr,omitempty"` -} // Type for latitude coordinate. type LatitudeType struct { Value float64 `xml:",chardata"` - LatitudeBaseType + FloatType Datum string `xml:"datum,attr,omitempty"` } @@ -223,17 +255,10 @@ type LogType struct { Entry []CommentType `xml:"Entry,omitempty"` } -type LongitudeBaseType struct { - Value float64 `xml:",chardata"` - Unit string `xml:"unit,attr,omitempty"` - PlusError *float64 `xml:"plusError,attr,omitempty"` - MinusError *float64 `xml:"minusError,attr,omitempty"` -} - // Type for longitude coordinate. type LongitudeType struct { Value float64 `xml:",chardata"` - LongitudeBaseType + FloatType Datum string `xml:"datum,attr,omitempty"` } @@ -243,8 +268,9 @@ type LongitudeType struct { // more Stations. type NetworkType struct { BaseNodeType - TotalNumberStations int `xml:"TotalNumberStations"` - SelectedNumberStations int `xml:"SelectedNumberStations"` + Operator []Operator `xml:"Operator,omitempty"` + TotalNumberStations CounterType `xml:"TotalNumberStations,omitempty"` + SelectedNumberStations CounterType `xml:"SelectedNumberStations,omitempty"` Station []StationType `xml:"Station,omitempty"` } @@ -259,7 +285,7 @@ type NumeratorCoefficient struct { } type Operator struct { - Agency []string `xml:"Agency,omitempty"` + Agency string `xml:"Agency"` Contact []PersonType `xml:"Contact,omitempty"` WebSite string `xml:"WebSite,omitempty"` } @@ -271,14 +297,14 @@ type PersonType struct { Phone []PhoneNumberType `xml:"Phone,omitempty"` } -// Must match the pattern [0-9]+-[0-9]+ +// PhoneNumber must match pattern [0-9]+-[0-9]+ type PhoneNumber string type PhoneNumberType struct { - Description string `xml:"description,attr,omitempty"` - CountryCode *int `xml:"CountryCode,omitempty"` - AreaCode *int `xml:"AreaCode,omitempty"` - PhoneNumber *PhoneNumber `xml:"PhoneNumber,omitempty"` + Description string `xml:"description,attr,omitempty"` + CountryCode *int `xml:"CountryCode,omitempty"` + AreaCode int `xml:"AreaCode"` + PhoneNumber PhoneNumber `xml:"PhoneNumber"` } type PoleZeroType struct { @@ -293,7 +319,7 @@ type PolesZerosType struct { BaseFilterType PzTransferFunctionType *PzTransferFunctionType `xml:"PzTransferFunctionType,omitempty"` NormalizationFactor *float64 `xml:"NormalizationFactor,omitempty"` - NormalizationFrequency *FrequencyType `xml:"NormalizationFrequency,omitempty"` + NormalizationFrequency *FloatType `xml:"NormalizationFrequency,omitempty"` Zero []PoleZeroType `xml:"Zero,omitempty"` Pole []PoleZeroType `xml:"Pole,omitempty"` } @@ -305,8 +331,8 @@ type PolynomialType struct { BaseFilterType Number *int `xml:"number,attr,omitempty"` ApproximationType *ApproximationType `xml:"ApproximationType,omitempty"` - FrequencyLowerBound *FrequencyType `xml:"FrequencyLowerBound,omitempty"` - FrequencyUpperBound *FrequencyType `xml:"FrequencyUpperBound,omitempty"` + FrequencyLowerBound *FloatType `xml:"FrequencyLowerBound,omitempty"` + FrequencyUpperBound *FloatType `xml:"FrequencyUpperBound,omitempty"` ApproximationLowerBound *float64 `xml:"ApproximationLowerBound,omitempty"` ApproximationUpperBound *float64 `xml:"ApproximationUpperBound,omitempty"` MaximumError *float64 `xml:"MaximumError,omitempty"` @@ -317,9 +343,9 @@ type PolynomialType struct { type PzTransferFunctionType string type ResponseListElementType struct { - Frequency *FrequencyType `xml:"Frequency,omitempty"` - Amplitude *FloatType `xml:"Amplitude,omitempty"` - Phase *AngleType `xml:"Phase,omitempty"` + Frequency FrequencyType `xml:"Frequency"` + Amplitude FloatType `xml:"Amplitude"` + Phase AngleType `xml:"Phase"` } // Response: list of frequency, amplitude and phase values. Corresponds @@ -350,32 +376,20 @@ type ResponseType struct { Stage []ResponseStageType `xml:"Stage,omitempty"` } -// May be one of open, closed, partial +// RestrictedStatusType: open, closed, partial type RestrictedStatusType string +const ( + RestrictedStatusOpen RestrictedStatusType = "open" + RestrictedStatusClosed RestrictedStatusType = "closed" + RestrictedStatusPartial RestrictedStatusType = "partial" +) + type SampleRateRatioType struct { NumberSamples *int `xml:"NumberSamples,omitempty"` NumberSeconds *int `xml:"NumberSeconds,omitempty"` } -// Sample rate in samples per second. -type SampleRateType struct { - Value float64 `xml:",chardata"` - Unit string `xml:"unit,attr,omitempty"` - PlusError *float64 `xml:"plusError,attr,omitempty"` - MinusError *float64 `xml:"minusError,attr,omitempty"` -} - -// A time value in seconds. -// -//nolint:deadcode,unused // Struct based on FDSN spec so keep it -type SecondType struct { - Value float64 `xml:",chardata"` - Unit string `xml:"unit,attr,omitempty"` - PlusError *float64 `xml:"plusError,attr,omitempty"` - MinusError *float64 `xml:"minusError,attr,omitempty"` -} - // Sensitivity and frequency ranges. The FrequencyRangeGroup is an // optional construct that defines a pass band in Hertz (FrequencyStart and // FrequencyEnd) in which the SensitivityValue is valid within the number of decibels @@ -391,7 +405,7 @@ type SensitivityType struct { type SiteType struct { Items []string `xml:",any,omitempty"` - Name string `xml:"Name,omitempty"` + Name string `xml:"Name"` Description string `xml:"Description,omitempty"` Town string `xml:"Town,omitempty"` County string `xml:"County,omitempty"` @@ -404,58 +418,89 @@ type SiteType struct { // start and end dates. type StationType struct { BaseNodeType - Latitude *LatitudeType `xml:"Latitude,omitempty"` - Longitude *LongitudeType `xml:"Longitude,omitempty"` - Elevation *DistanceType `xml:"Elevation,omitempty"` - Site *SiteType `xml:"Site,omitempty"` + Latitude LatitudeType `xml:"Latitude"` + Longitude LongitudeType `xml:"Longitude"` + Elevation DistanceType `xml:"Elevation"` + Site SiteType `xml:"Site"` + WaterLevel *FloatType `xml:"WaterLevel,omitempty"` Vault string `xml:"Vault,omitempty"` Geology string `xml:"Geology,omitempty"` Equipment []EquipmentType `xml:"Equipment,omitempty"` Operator []Operator `xml:"Operator,omitempty"` - Agency []string `xml:"Agency,omitempty"` - Contact []PersonType `xml:"Contact,omitempty"` - WebSite string `xml:"WebSite,omitempty"` CreationDate xsdDateTime `xml:"CreationDate,omitempty"` TerminationDate xsdDateTime `xml:"TerminationDate,omitempty"` - TotalNumberChannels int `xml:"TotalNumberChannels"` - SelectedNumberChannels int `xml:"SelectedNumberChannels"` + TotalNumberChannels CounterType `xml:"TotalNumberChannels,omitempty"` + SelectedNumberChannels int `xml:"SelectedNumberChannels,omitempty"` ExternalReference []ExternalReferenceType `xml:"ExternalReference,omitempty"` Channel []ChannelType `xml:"Channel,omitempty"` } -// May be one of NONE, EVEN, ODD +// Symmetry: NONE, EVEN, ODD type Symmetry string -// May be one of TRIGGERED, CONTINUOUS, HEALTH, GEOPHYSICAL, WEATHER, FLAG, SYNTHESIZED, INPUT, EXPERIMENTAL, MAINTENANCE, BEAM +const ( + SymmetryNone Symmetry = "NONE" + SymmetryEven Symmetry = "EVEN" + SymmetryOdd Symmetry = "ODD" +) + +// Channel types: TRIGGERED, CONTINUOUS, HEALTH, GEOPHYSICAL, WEATHER, FLAG, SYNTHESIZED, INPUT, EXPERIMENTAL, MAINTENANCE, BEAM type Type string +const ( + TypeTriggered Type = "TRIGGERED" + TypeContinuous Type = "CONTINUOUS" + TypeHealth Type = "HEALTH" + TypeGeophysical Type = "GEOPHYSICAL" + TypeWeather Type = "WEATHER" + TypeFlag Type = "FLAG" + TypeSynthesized Type = "SYNTHESIZED" + TypeInput Type = "INPUT" + TypeExperimental Type = "EXPERIMENTAL" + TypeMaintenance Type = "MAINTENANCE" + TypeBeam Type = "BEAM" +) + type UnitsType struct { Name string `xml:"Name,omitempty"` Description string `xml:"Description,omitempty"` } -//nolint:deadcode,unused // Struct based on FDSN spec so keep it -type VoltageType struct { - Value float64 `xml:",chardata"` - Unit string `xml:"unit,attr,omitempty"` - PlusError *float64 `xml:"plusError,attr,omitempty"` - MinusError *float64 `xml:"minusError,attr,omitempty"` -} - type xsdDateTime time.Time func (t *xsdDateTime) UnmarshalText(text []byte) error { - return _unmarshalTime(text, (*time.Time)(t), "2006-01-02T15:04:05.999999999") + return _unmarshalTime(text, (*time.Time)(t)) } func (t *xsdDateTime) MarshalText() ([]byte, error) { - return []byte((*time.Time)(t).Format("2006-01-02T15:04:05.999999999")), nil + return []byte((*time.Time)(t).UTC().Format(time.RFC3339Nano)), nil } -func _unmarshalTime(text []byte, t *time.Time, format string) (err error) { + +// Helper methods for backward compatibility +func (c CounterType) ToInt() int { + return int(c) +} + +func IntToCounterType(i int) *CounterType { + c := CounterType(i) + return &c +} +func _unmarshalTime(text []byte, t *time.Time) (err error) { s := string(bytes.TrimSpace(text)) - *t, err = time.Parse(format, s) - if _, ok := err.(*time.ParseError); ok { - *t, err = time.Parse(format+"Z07:00", s) + + // Try RFC3339Nano first (new format) + *t, err = time.Parse(time.RFC3339Nano, s) + if err == nil { + return nil + } + + // Try legacy format for backward compatibility + *t, err = time.Parse("2006-01-02T15:04:05.999999999", s) + if err == nil { + return nil } + + // Try legacy format with timezone + *t, err = time.Parse("2006-01-02T15:04:05.999999999Z07:00", s) return err } diff --git a/cmd/fdsn-ws/server.go b/cmd/fdsn-ws/server.go index c10ec50..452b969 100644 --- a/cmd/fdsn-ws/server.go +++ b/cmd/fdsn-ws/server.go @@ -26,7 +26,7 @@ var ( LOG_EXTRA bool // Whether POST body is logged. ) -var stationVersion = "1.1" +var stationVersion = "1.2" var eventVersion = "1.2" var dataselectVersion = "1.1" var zeroDateTime = time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC) diff --git a/internal/fdsn/dataselect.go b/internal/fdsn/dataselect.go index 229d614..708caff 100644 --- a/internal/fdsn/dataselect.go +++ b/internal/fdsn/dataselect.go @@ -93,15 +93,33 @@ func (t *Time) UnmarshalText(text []byte) (err error) { return nil } + // Try RFC3339Nano first (new format) + t.Time, err = time.Parse(time.RFC3339Nano, s) + if err == nil { + return nil + } + + // Try with Z suffix added for backward compatibility if !strings.HasSuffix(s, "Z") { - s = s + "Z" + t.Time, err = time.Parse(time.RFC3339Nano, s+"Z") + if err == nil { + return nil + } } - t.Time, err = time.Parse(time.RFC3339Nano, s) - if err != nil { - return fmt.Errorf("invalid time format: %s", s) + // Try legacy format for backward compatibility + t.Time, err = time.Parse("2006-01-02T15:04:05.999999999", s) + if err == nil { + return nil } - return nil + + // Try legacy format with timezone + t.Time, err = time.Parse("2006-01-02T15:04:05.999999999Z07:00", s) + if err == nil { + return nil + } + + return fmt.Errorf("invalid time format: %s", s) } // This function helps to expose the unmarshaler to the public