Skip to content

Commit

Permalink
Support SeisComP3 XML inventory
Browse files Browse the repository at this point in the history
  • Loading branch information
bclswl0827 committed Mar 5, 2024
1 parent 31c718c commit 9d61c88
Show file tree
Hide file tree
Showing 75 changed files with 594 additions and 256 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

Starting from v2.2.5, all notable changes to this project will be documented in this file.

## v2.10.0

- Update frontend dependencies credits in README
- Fixed the validation error of the datetime picker in the frontend
- New API endpoint `/api/v1/inventory` to get SeisComP3 XML inventory data
- Removed unused configuration fields, change geophone sensitivity unit to `V/m/s`

## v2.9.0

- Frontend refactoring: use functional components and hooks
Expand Down
137 changes: 72 additions & 65 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<img src="https://raw.githubusercontent.com/anyshake/logotype/master/banner_observer.png" width="500"/>
<img src="https://raw.githubusercontent.com/anyshake/logotype/master/banner_observer.png" width="500" alt="banner" />
</p>

[![MIT License](https://img.shields.io/badge/license-MIT-green)](https://github.com/anyshake/observer/blob/master/LICENSE)
Expand All @@ -23,23 +23,23 @@ Please visit [anyshake.org/docs/introduction](https://anyshake.org/docs/introduc

## Features

- User-friendly web-based interface
- Mobile / Tablet friendly interface
- Query seismic waveform by time range
- Query seismic waveform by known event
- Link to share the seismic waveform
- Real-time seismic waveform display
- Swagger generated API documentation
- Support multiple database engines
- Support multiple languages, detected by browser
- Multiple seismic intensity standards, default to JMA
- Cross-platform, runs on Linux, Windows, macOS
- Ability to stream seismic data via SeedLink protocol
- Ability to export data to SAC or MiniSEED format
- AnyShake Explorer data checksum verification
- Auto reset AnyShake Explorer on error
- Flexible channel packet read length
- Variable serial port baud rate
- User-friendly web-based interface
- Mobile / Tablet friendly interface
- Query seismic waveform by time range
- Query seismic waveform by known event
- Link to share the seismic waveform
- Real-time seismic waveform display
- Swagger generated API documentation
- Support multiple database engines
- Support multiple languages, detected by browser
- Multiple seismic intensity standards, default to JMA
- Cross-platform, runs on Linux, Windows, macOS
- Ability to stream seismic data via SeedLink protocol
- Ability to export data to SAC or MiniSEED format
- AnyShake Explorer data checksum verification
- Auto reset AnyShake Explorer on error
- Flexible channel packet read length
- Variable serial port baud rate

## Preview

Expand All @@ -57,57 +57,64 @@ Thanks to the following tools and libraries, AnyShake Observer is made possible!

### Backend

- [github.com/PuerkitoBio/goquery](https://github.com/PuerkitoBio/goquery)
- [github.com/bclswl0827/go-serial](https://github.com/bclswl0827/go-serial)
- [github.com/bclswl0827/sacio](https://github.com/bclswl0827/sacio)
- [github.com/beevik/ntp](https://github.com/beevik/ntp)
- [github.com/common-nighthawk/go-figure](https://github.com/common-nighthawk/go-figure)
- [github.com/gin-contrib/gzip](https://github.com/gin-contrib/gzip)
- [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin)
- [github.com/gorilla/websocket](https://github.com/gorilla/websocket)
- [github.com/juju/ratelimit](https://github.com/juju/ratelimit)
- [github.com/mackerelio/go-osstat](https://github.com/mackerelio/go-osstat)
- [github.com/sbabiv/xml2map](https://github.com/sbabiv/xml2map)
- [github.com/shirou/gopsutil](https://github.com/shirou/gopsutil)
- [github.com/swaggo/files](https://github.com/swaggo/files)
- [github.com/swaggo/swag](https://github.com/swaggo/swag)
- [github.com/wille/osutil](https://github.com/wille/osutil)
- [gorm.io/driver/mysql](https://github.com/go-gorm/mysql)
- [gorm.io/driver/postgres](https://github.com/go-gorm/postgres)
- [gorm.io/driver/sqlite](https://github.com/go-gorm/sqlite)
- [gorm.io/driver/sqlserver](https://github.com/go-gorm/sqlserver)
- [gorm.io/gorm](https://gorm.io/)
- [github.com/bclswl0827/mseedio](https://github.com/bclswl0827/mseedio)
- [github.com/fatih/color](https://github.com/fatih/color)
- [github.com/json-iterator/go](https://github.com/json-iterator/go)
- [github.com/swaggo/gin-swagger](https://github.com/swaggo/gin-swagger)
- [github.com/PuerkitoBio/goquery](https://github.com/PuerkitoBio/goquery)
- [github.com/bclswl0827/go-serial](https://github.com/bclswl0827/go-serial)
- [github.com/bclswl0827/sacio](https://github.com/bclswl0827/sacio)
- [github.com/beevik/ntp](https://github.com/beevik/ntp)
- [github.com/common-nighthawk/go-figure](https://github.com/common-nighthawk/go-figure)
- [github.com/gin-contrib/gzip](https://github.com/gin-contrib/gzip)
- [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin)
- [github.com/gorilla/websocket](https://github.com/gorilla/websocket)
- [github.com/juju/ratelimit](https://github.com/juju/ratelimit)
- [github.com/mackerelio/go-osstat](https://github.com/mackerelio/go-osstat)
- [github.com/sbabiv/xml2map](https://github.com/sbabiv/xml2map)
- [github.com/shirou/gopsutil](https://github.com/shirou/gopsutil)
- [github.com/swaggo/files](https://github.com/swaggo/files)
- [github.com/swaggo/swag](https://github.com/swaggo/swag)
- [github.com/wille/osutil](https://github.com/wille/osutil)
- [gorm.io/driver/mysql](https://github.com/go-gorm/mysql)
- [gorm.io/driver/postgres](https://github.com/go-gorm/postgres)
- [gorm.io/driver/sqlite](https://github.com/go-gorm/sqlite)
- [gorm.io/driver/sqlserver](https://github.com/go-gorm/sqlserver)
- [gorm.io/gorm](https://gorm.io/)
- [github.com/bclswl0827/mseedio](https://github.com/bclswl0827/mseedio)
- [github.com/fatih/color](https://github.com/fatih/color)
- [github.com/json-iterator/go](https://github.com/json-iterator/go)
- [github.com/swaggo/gin-swagger](https://github.com/swaggo/gin-swagger)

### Frontend

- [axios](https://axios-http.com/)
- [date-fns](https://date-fns.org/)
- [highcharts](https://www.highcharts.com/)
- [i18next](https://www.i18next.com/)
- [i18next-browser-languagedetector](https://github.com/i18next/i18next-browser-languageDetector)
- [i18next-http-backend](https://github.com/i18next/i18next-http-backend)
- [js-file-download](https://github.com/kennethjiang/js-file-download)
- [leaflet](https://leafletjs.com/)
- [mui](https://mui.com/)
- [react](https://reactjs.org/)
- [react-dom](https://reactjs.org/)
- [react-hot-toast](https://react-hot-toast.com/)
- [react-i18next](https://react.i18next.com/)
- [react-leaflet](https://react-leaflet.js.org/)
- [react-polling](https://github.com/vivek12345/react-polling)
- [react-redux](https://react-redux.js.org/)
- [react-router-dom](https://reactrouter.com/)
- [react-scripts](https://github.com/facebook/create-react-app/tree/main/packages/react-scripts)
- [redux](https://react-redux.js.org/)
- [redux-persist](https://github.com/rt2zz/redux-persist)
- [seisplotjs](https://github.com/crotwell/seisplotjs)
- [tailwindcss](https://tailwindcss.com/)
- [typescript](https://www.typescriptlang.org/)
- [emotion](https://github.com/emotion-js/emotion)
- [mui](https://mui.com/)
- [reduxjs/toolkit](https://redux-toolkit.js.org/)
- [axios](https://axios-http.com/)
- [date-fns](https://date-fns.org/)
- [file-saver](https://github.com/eligrey/FileSaver.js)
- [highcharts](https://www.highcharts.com/)
- [highcharts-react-official](https://github.com/highcharts/highcharts-react)
- [i18next](https://www.i18next.com/)
- [i18next-browser-languagedetector](https://github.com/i18next/i18next-browser-languageDetector)
- [leaflet](https://leafletjs.com/)
- [oregondsp](https://github.com/crotwell/OregonDSP-kotlin)
- [react](https://reactjs.org/)
- [react-dom](https://reactjs.org/)
- [react-hot-toast](https://react-hot-toast.com/)
- [react-i18next](https://react.i18next.com/)
- [react-leaflet](https://react-leaflet.js.org/)
- [react-polling](https://github.com/vivek12345/react-polling)
- [react-redux](https://react-redux.js.org/)
- [react-router-dom](https://reactrouter.com/)
- [react-scripts](https://github.com/facebook/create-react-app/tree/main/packages/react-scripts)
- [react-syntax-highlighter](https://github.com/react-syntax-highlighter/react-syntax-highlighter)
- [redux](https://react-redux.js.org/)
- [redux-persist](https://github.com/rt2zz/redux-persist)
- [cross-env](https://github.com/kentcdodds/cross-env)
- [tailwindcss](https://tailwindcss.com/)
- [tailwindcss-animated](https://tailwindcss-animated.com/)
- [typescript](https://www.typescriptlang.org/)

## License

[The MIT License (MIT)](https://raw.githubusercontent.com/anyshake/observer/master/LICENSE)

![Star History Chart](https://api.star-history.com/svg?repos=anyshake/observer&type=Date)
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v2.9.0
v2.10.0
34 changes: 34 additions & 0 deletions app/v1/inventory/module.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package inventory

import (
"net/http"

"github.com/anyshake/observer/app"
"github.com/anyshake/observer/server/response"
"github.com/gin-gonic/gin"
)

// @Summary AnyShake Observer station inventory
// @Description Get SeisComP XML inventory, which contains meta data of the station
// @Router /inventory [get]
// @Param format query string false "Format of the inventory, either `json` or `xml`", default is `xml`
// @Produce application/json
// @Success 200 {object} response.HttpResponse{data=string} "Successfully get SeisComP XML inventory"
// @Produce application/xml
func (i *Inventory) RegisterModule(rg *gin.RouterGroup, options *app.ServerOptions) {
rg.GET("/inventory", func(c *gin.Context) {
var binding Binding
if err := c.ShouldBind(&binding); err != nil {
response.Error(c, http.StatusBadRequest)
return
}

inventory := getInventoryString(options.FeatureOptions.Config, options.FeatureOptions.Status)
if binding.Format == "json" {
response.Message(c, "Successfully get SeisComP XML inventory", inventory)
return
}

c.Data(http.StatusOK, "application/xml", []byte(inventory))
})
}
181 changes: 181 additions & 0 deletions app/v1/inventory/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package inventory

import (
"fmt"
"math"

"github.com/anyshake/observer/config"
"github.com/anyshake/observer/publisher"
)

func getInventoryString(config *config.Conf, status *publisher.Status) string {
const xmlTemplate = `<?xml version="1.0" encoding="UTF-8"?>
<seiscomp xmlns="http://geofon.gfz-potsdam.de/ns/seiscomp3-schema/0.10" version="0.10">
<Inventory>
<sensor publicID="Sensor@AnyShake" name="Geophone" response="ResponsePAZ@AnyShake">
<type>SPS</type>
<unit>M/S</unit>
<lowFrequency>0.1</lowFrequency>
<highFrequency>%d</highFrequency>
</sensor>
<datalogger publicID="Datalogger@AnyShake" name="ADC_WITH_FULLSCALE">
<description>Analog to digital converter</description>
<gain>%f</gain>
<decimation sampleRateNumerator="%d" sampleRateDenominator="1" />
</datalogger>
<responsePAZ publicID="ResponsePAZ@AnyShake" name="Geophone">
<type>A</type>
<gain>%f</gain>
<gainFrequency>%f</gainFrequency>
<normalizationFactor>1</normalizationFactor>
<normalizationFrequency>%f</normalizationFrequency>
</responsePAZ>
<network publicID="Network@AnyShake" code="%s">
<start>%s</start>
<description>AnyShake_Project_Seismic_Network</description>
<institutions>AnyShake Project</institutions>
<region>%s</region>
<shared>true</shared>
<type>BB</type>
<station publicID="Station@AnyShake" code="%s">
<start>%s</start>
<description>%s</description>
<latitude>%f</latitude>
<longitude>%f</longitude>
<elevation>%f</elevation>
<place>%s</place>
<country>%s</country>
<affiliation>%s</affiliation>
<type>BB</type>
<shared>true</shared>
<sensorLocation publicID="SensorLocation@AnyShake" code="%s">
<start>%s</start>
<latitude>%f</latitude>
<longitude>%f</longitude>
<elevation>%f</elevation>
<stream publicID="Stream#EHN" code="EHN" datalogger="Datalogger@AnyShake"
sensor="Sensor@AnyShake">
<start>%s</start>
<sampleRateNumerator>%d</sampleRateNumerator>
<sampleRateDenominator>1</sampleRateDenominator>
<depth>0</depth>
<azimuth>0</azimuth>
<dip>0</dip>
<gain>%f</gain>
<gainFrequency>1</gainFrequency>
<gainUnit>M/S</gainUnit>
<format>Steim2</format>
<flags>GC</flags>
<shared>true</shared>
</stream>
<stream publicID="Stream#EHE" code="EHE" datalogger="Datalogger@AnyShake"
sensor="Sensor@AnyShake">
<start>%s</start>
<sampleRateNumerator>%d</sampleRateNumerator>
<sampleRateDenominator>1</sampleRateDenominator>
<depth>0</depth>
<azimuth>90</azimuth>
<dip>0</dip>
<gain>%f</gain>
<gainFrequency>1</gainFrequency>
<gainUnit>M/S</gainUnit>
<format>Steim2</format>
<flags>GC</flags>
<shared>true</shared>
</stream>
<stream publicID="Stream#EHZ" code="EHZ" datalogger="Datalogger@AnyShake"
sensor="Sensor@AnyShake">
<start>%s</start>
<sampleRateNumerator>%d</sampleRateNumerator>
<sampleRateDenominator>1</sampleRateDenominator>
<depth>0</depth>
<azimuth>0</azimuth>
<dip>90</dip>
<gain>%f</gain>
<gainFrequency>1</gainFrequency>
<gainUnit>M/S</gainUnit>
<format>Steim2</format>
<flags>GC</flags>
<shared>true</shared>
</stream>
</sensorLocation>
</station>
</network>
</Inventory>
</seiscomp>
`

startTime := status.ReadyTime
currentSampleRate := (len(status.Geophone.EHZ) + len(status.Geophone.EHE) + len(status.Geophone.EHN)) / 3
if startTime.IsZero() || currentSampleRate == 0 {
return ""
}

sensorHighFrequency := currentSampleRate / 2
dataloggerGain := math.Pow(2, float64(config.ADC.Resolution-1)) / config.ADC.FullScale
dataloggerSampleRateNumerator := currentSampleRate
responsePAZGain := config.Geophone.Sensitivity
responsePAZGainFrequency := config.Geophone.Frequency
responsePAZGainNormalizationFrequency := config.Geophone.Frequency
networkCode := config.Station.Network
networkStart := status.ReadyTime.UTC().Format("2006-01-02T15:04:05.0000Z")
networkRegion := config.Station.Region
stationCode := config.Station.Station
stationStart := status.ReadyTime.UTC().Format("2006-01-02T15:04:05.0000Z")
stationDescription := fmt.Sprintf("AnyShake Station %s", config.Station.UUID)
stationLatitude := config.Station.Latitude
stationLongitude := config.Station.Longitude
stationElevation := config.Station.Elevation
stationCity := config.Station.City
stationCountry := config.Station.Country
stationAffiliation := config.Station.Owner
sensorLocationCode := config.Station.Location
sensorLocationStart := status.ReadyTime.UTC().Format("2006-01-02T15:04:05.0000Z")
sensorLocationLatitude := config.Station.Latitude
sensorLocationLongitude := config.Station.Longitude
sensorLocationElevation := config.Station.Elevation

// Stream settings
streamStart := status.ReadyTime.UTC().Format("2006-01-02T15:04:05.0000Z")
streamSampleRateNumerator := currentSampleRate
streamGain := dataloggerGain * config.Geophone.Sensitivity

return fmt.Sprintf(
xmlTemplate,
sensorHighFrequency,
dataloggerGain,
dataloggerSampleRateNumerator,
responsePAZGain,
responsePAZGainFrequency,
responsePAZGainNormalizationFrequency,
networkCode,
networkStart,
networkRegion,
stationCode,
stationStart,
stationDescription,
stationLatitude,
stationLongitude,
stationElevation,
stationCity,
stationCountry,
stationAffiliation,
sensorLocationCode,
sensorLocationStart,
sensorLocationLatitude,
sensorLocationLongitude,
sensorLocationElevation,
// Stream for EHZ
streamStart,
streamSampleRateNumerator,
streamGain,
// Stream for EHE
streamStart,
streamSampleRateNumerator,
streamGain,
// Stream for EHN
streamStart,
streamSampleRateNumerator,
streamGain,
)
}
Loading

0 comments on commit 9d61c88

Please sign in to comment.