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}