1package stations
  2
  3import (
  4	"bufio"
  5	"encoding/json"
  6	"encoding/xml"
  7	"ewdetect/config"
  8	"io"
  9	"net/http"
 10	"os"
 11	"strconv"
 12	"strings"
 13
 14	"github.com/rs/zerolog/log"
 15)
 16
 17type Station struct {
 18	Name       string
 19	Lat        float64
 20	Lon        float64
 21	SampleRate int // Hz
 22	DC         int32
 23	NoiseFloor int32
 24	Calibrated bool
 25}
 26
 27type FDSNStationXML struct {
 28	XMLName  xml.Name      `xml:"FDSNStationXML"`
 29	Stations []FDSNStation `xml:"Network>Station"`
 30}
 31
 32type FDSNStation struct {
 33	Code      string   `xml:"code,attr"`
 34	Latitude  float64  `xml:"Latitude"`
 35	Longitude float64  `xml:"Longitude"`
 36	Site      FDSNSite `xml:"Site"`
 37}
 38
 39type FDSNSite struct {
 40	Name string `xml:"Name"`
 41}
 42
 43var Stations = make(map[string]*map[string]Station)
 44
 45var URLs = []string{
 46	"auspass.edu.au:18000",
 47	"eida.bgr.de:18000",
 48	"www.cismid.uni.edu.pe:18000",
 49	"ephesite.ens.fr:18000",
 50	"geofon.gfz-potsdam.de:18000",
 51	"link.geonet.org.nz:18000",
 52	"seis-pub.ga.gov.au:18000",
 53	"89.22.182.133:18000",
 54	"finseis.seismo.helsinki.fi:18000",
 55	"ayiti.unice.fr:18000",
 56	"ws.icgc.cat:18000",
 57	"rtserve.ida.ucsd.edu:18000",
 58	"data.ifz.ru:18000",
 59	"rtserver.ipgp.fr:18000",
 60	"rtserve.iris.washington.edu:18000",
 61	"jamaseis.iris.edu:18000",
 62	"185.15.171.86:18000",
 63	"erde.geophysik.uni-muenchen.de:18000",
 64	"195.96.231.100:18000",
 65	"earthquakescanada.nrcan.gc.ca:18000",
 66	"obsebre.es:18000",
 67	"nam.ogs.it:18000",
 68	"rtserve.ou.edu:18000",
 69	"eida.orfeus-eu.org:18000",
 70	"hudson.igf.edu.pl:18000",
 71	"161.35.236.45:18000",
 72	"helis.redsismicabaru.com:18000",
 73	"rtserve.resif.fr:18000",
 74	"147.213.113.73:18000",
 75	"rsis1.on.br:18000",
 76	"eeyore.seis.sc.edu:6382",
 77	"rtserve.ird.nc:18000",
 78	"vibrato.staneo.fr:18000",
 79	"snac.gein.noa.gr:18000",
 80	"rtserve.beg.utexas.edu:18000",
 81	"119.46.126.38:18000",
 82	"sislink.geofisica.ufrn.br:18000",
 83	"www.sismocal.org:18000",
 84	"rtweb.units.it:18000",
 85	"seedsrv0.ovmp.martinique.univ-ag.fr:18000",
 86	"clv-cge.uevora.pt:18000",
 87	"148.213.24.15:18000",
 88	"worm.uprm.edu:18000",
 89	"cwbpub.cr.usgs.gov:18000",
 90	"seisrequest.iag.usp.br:18000",
 91}
 92
 93func GetPipeDelimited(stationMap *map[string]Station, URL string, SampleRate int) {
 94	resp, err := http.Get(URL)
 95	if err != nil {
 96		log.Error().Err(err).Str("url", URL).Msg("Failed to get pipe delimited station data")
 97		return
 98	}
 99	defer resp.Body.Close()
100
101	body, err := io.ReadAll(resp.Body)
102	if err != nil {
103		log.Error().Err(err).Msg("Failed to read response body")
104		return
105	}
106
107	scanner := bufio.NewScanner(strings.NewReader(string(body)))
108	for scanner.Scan() {
109		line := scanner.Text()
110		if strings.HasPrefix(line, "#") {
111			continue
112		}
113		fields := strings.Split(line, "|")
114		if len(fields) < 7 {
115			continue
116		}
117		lat, _ := strconv.ParseFloat(fields[2], 64)
118		lon, _ := strconv.ParseFloat(fields[3], 64)
119		(*stationMap)[fields[1]] = Station{
120			Name:       fields[5],
121			Lat:        lat,
122			Lon:        lon,
123			SampleRate: SampleRate,
124		}
125		log.Debug().Str("station", fields[1]).Str("name", fields[5]).Msg("Added station")
126	}
127}
128
129func GetXMLStations(stationMap *map[string]Station, URL string, SampleRate int) {
130	resp, err := http.Get(URL)
131	if err != nil {
132		log.Error().Err(err).Str("url", URL).Msg("Failed to get XML station data")
133		return
134	}
135	defer resp.Body.Close()
136
137	var fdsn FDSNStationXML
138	decoder := xml.NewDecoder(resp.Body)
139	err = decoder.Decode(&fdsn)
140	if err != nil {
141		log.Error().Err(err).Msg("Failed to decode XML response")
142		return
143	}
144
145	for _, station := range fdsn.Stations {
146		(*stationMap)[station.Code] = Station{
147			Name:       station.Site.Name,
148			Lat:        station.Latitude,
149			Lon:        station.Longitude,
150			SampleRate: SampleRate,
151		}
152		log.Debug().Str("station", station.Code).Str("name", station.Site.Name).Msg("Added station")
153	}
154}
155
156func Init() {
157	log.Info().Msg("Gathering station metadata")
158	if !config.FastStartup {
159		log.Info().Msg("Initializing NZ stations")
160		NZ := make(map[string]Station)
161		Stations["link.geonet.org.nz:18000"] = &NZ
162		GetPipeDelimited(&NZ, "https://beta-service.geonet.org.nz/fdsnws/station/1/query?&format=text&level=station", 100)
163
164		log.Info().Msg("Initializing IRIS stations")
165		IRIS := make(map[string]Station)
166		Stations["rtserve.iris.washington.edu:18000"] = &IRIS
167		GetPipeDelimited(&IRIS, "https://service.iris.edu/fdsnws/station/1/query?net=_REALTIME&level=station&format=text&includecomments=true&nodata=404", 100)
168
169		log.Info().Msg("Initializing RESIF stations")
170		RESIF := make(map[string]Station)
171		Stations["rtserve.resif.fr:18000"] = &RESIF
172		GetXMLStations(&RESIF, "https://ws.resif.fr/fdsnws/station/1/query", 100)
173
174		log.Info().Msg("Initializing USP-IAG stations")
175		USPIAG := make(map[string]Station)
176		Stations["seisrequest.iag.usp.br:18000"] = &USPIAG
177		GetXMLStations(&RESIF, "https://seisrequest.iag.usp.br/fdsnws/station/1/query", 100)
178
179		log.Info().Msg("Initializing BGR stations")
180		BGR := make(map[string]Station)
181		Stations["eida.bgr.de:18000"] = &BGR
182		GetXMLStations(&BGR, "https://eida.bgr.de/fdsnws/station/1/query", 100)
183
184		log.Info().Msg("Initializing GEOFON stations")
185		GEOFON := make(map[string]Station)
186		Stations["geofon.gfz-potsdam.de:18000"] = &GEOFON
187		GetXMLStations(&GEOFON, "http://geofon.gfz-potsdam.de/fdsnws/station/1/query?endafter=2024-01-01T00:00:00Z", 100)
188
189		log.Info().Msg("Initializing ICGC stations")
190		ICGC := make(map[string]Station)
191		Stations["ws.icgc.cat:18000"] = &ICGC
192		GetXMLStations(&ICGC, "https://ws.icgc.cat/fdsnws/station/1/query", 100)
193
194		log.Info().Msg("Initializing LMU stations")
195		LMU := make(map[string]Station)
196		Stations["erde.geophysik.uni-muenchen.de:18000"] = &LMU
197		GetXMLStations(&LMU, "https://erde.geophysik.uni-muenchen.de/fdsnws/station/1/query", 100)
198
199		log.Info().Msg("Initializing ORFEUS stations")
200		ORFEUS := make(map[string]Station)
201		Stations["eida.orfeus-eu.org:18000"] = &ORFEUS
202		GetXMLStations(&ORFEUS, "https://www.orfeus-eu.org/fdsnws/station/1/query", 100)
203
204		log.Info().Msg("Initializing NOA stations")
205		NOA := make(map[string]Station)
206		Stations["snac.gein.noa.gr:18000"] = &NOA
207		GetXMLStations(&NOA, "http://snac.gein.noa.gr:8080/fdsnws/station/1/query", 100)
208
209		log.Info().Msg("Initializing AusPass stations")
210		AUSPASS := make(map[string]Station)
211		Stations["auspass.edu.au:18000"] = &AUSPASS
212		GetXMLStations(&AUSPASS, "http://auspass.edu.au:8080/fdsnws/station/1/query", 100)
213
214		log.Info().Msg("Initializing IPGP stations")
215		IPGP := make(map[string]Station)
216		Stations["rtserver.ipgp.fr:18000"] = &IPGP
217		GetXMLStations(&IPGP, "https://ws.ipgp.fr/fdsnws/station/1/query", 100)
218		log.Info().Msg("Station initialization complete")
219	} else {
220		log.Info().Msg("Loading stations from cache")
221		stationsJson, err := os.ReadFile("station-metadata-cache.json")
222		if err != nil {
223			log.Fatal().Err(err).Msg("Failed to read station cache file")
224		}
225
226		stationsData := make(map[string]map[string]Station)
227		err = json.Unmarshal(stationsJson, &stationsData)
228		if err != nil {
229			log.Fatal().Err(err).Msg("Failed to unmarshal station data")
230		}
231
232		for k, v := range stationsData {
233			stationMap := v
234			Stations[k] = &stationMap
235		}
236		log.Info().Msg("Successfully loaded stations from cache")
237	}
238}
239
240func SerializeStationData() {
241	log.Info().Msg("Serializing station data to cache")
242	stationsData := make(map[string]map[string]Station)
243	for k, v := range Stations {
244		stationsData[k] = *v
245	}
246
247	stationsJson, err := json.MarshalIndent(stationsData, "", "  ")
248	if err != nil {
249		log.Error().Err(err).Msg("Failed to marshal station data")
250		return
251	}
252
253	err = os.WriteFile("station-metadata-cache.json", stationsJson, 0644)
254	if err != nil {
255		log.Error().Err(err).Msg("Failed to write station cache file")
256		return
257	}
258	log.Info().Msg("Successfully serialized station data to cache")
259}