1use chrono::{DateTime, Utc};
31
32use crate::inventory::*;
33
34pub struct InventoryBuilder {
38 source: String,
39 sender: Option<String>,
40 created: Option<DateTime<Utc>>,
41 networks: Vec<Network>,
42}
43
44impl Inventory {
45 pub fn builder() -> InventoryBuilder {
47 InventoryBuilder {
48 source: String::new(),
49 sender: None,
50 created: None,
51 networks: vec![],
52 }
53 }
54}
55
56impl InventoryBuilder {
57 pub fn source(mut self, source: impl Into<String>) -> Self {
59 self.source = source.into();
60 self
61 }
62
63 pub fn sender(mut self, sender: impl Into<String>) -> Self {
65 self.sender = Some(sender.into());
66 self
67 }
68
69 pub fn created(mut self, created: DateTime<Utc>) -> Self {
71 self.created = Some(created);
72 self
73 }
74
75 pub fn network(
77 mut self,
78 code: impl Into<String>,
79 f: impl FnOnce(NetworkBuilder) -> NetworkBuilder,
80 ) -> Self {
81 let builder = f(NetworkBuilder::new(code));
82 self.networks.push(builder.build());
83 self
84 }
85
86 pub fn build(self) -> Inventory {
88 Inventory {
89 source: self.source,
90 sender: self.sender,
91 created: self.created,
92 networks: self.networks,
93 }
94 }
95}
96
97pub struct NetworkBuilder {
101 code: String,
102 description: Option<String>,
103 start_date: Option<DateTime<Utc>>,
104 end_date: Option<DateTime<Utc>>,
105 stations: Vec<Station>,
106}
107
108impl NetworkBuilder {
109 fn new(code: impl Into<String>) -> Self {
110 Self {
111 code: code.into(),
112 description: None,
113 start_date: None,
114 end_date: None,
115 stations: vec![],
116 }
117 }
118
119 pub fn description(mut self, desc: impl Into<String>) -> Self {
121 self.description = Some(desc.into());
122 self
123 }
124
125 pub fn start_date(mut self, date: DateTime<Utc>) -> Self {
127 self.start_date = Some(date);
128 self
129 }
130
131 pub fn end_date(mut self, date: DateTime<Utc>) -> Self {
133 self.end_date = Some(date);
134 self
135 }
136
137 pub fn station(
139 mut self,
140 code: impl Into<String>,
141 f: impl FnOnce(StationBuilder) -> StationBuilder,
142 ) -> Self {
143 let builder = f(StationBuilder::new(code));
144 self.stations.push(builder.build());
145 self
146 }
147
148 fn build(self) -> Network {
149 Network {
150 code: self.code,
151 description: self.description,
152 start_date: self.start_date,
153 end_date: self.end_date,
154 stations: self.stations,
155 }
156 }
157}
158
159pub struct StationBuilder {
163 code: String,
164 description: Option<String>,
165 latitude: f64,
166 longitude: f64,
167 elevation: f64,
168 site_name: String,
169 start_date: Option<DateTime<Utc>>,
170 end_date: Option<DateTime<Utc>>,
171 creation_date: Option<DateTime<Utc>>,
172 channels: Vec<Channel>,
173}
174
175impl StationBuilder {
176 fn new(code: impl Into<String>) -> Self {
177 Self {
178 code: code.into(),
179 description: None,
180 latitude: 0.0,
181 longitude: 0.0,
182 elevation: 0.0,
183 site_name: String::new(),
184 start_date: None,
185 end_date: None,
186 creation_date: None,
187 channels: vec![],
188 }
189 }
190
191 pub fn latitude(mut self, lat: f64) -> Self {
192 self.latitude = lat;
193 self
194 }
195
196 pub fn longitude(mut self, lon: f64) -> Self {
197 self.longitude = lon;
198 self
199 }
200
201 pub fn elevation(mut self, elev: f64) -> Self {
202 self.elevation = elev;
203 self
204 }
205
206 pub fn site_name(mut self, name: impl Into<String>) -> Self {
207 self.site_name = name.into();
208 self
209 }
210
211 pub fn description(mut self, desc: impl Into<String>) -> Self {
212 self.description = Some(desc.into());
213 self
214 }
215
216 pub fn start_date(mut self, date: DateTime<Utc>) -> Self {
217 self.start_date = Some(date);
218 self
219 }
220
221 pub fn end_date(mut self, date: DateTime<Utc>) -> Self {
222 self.end_date = Some(date);
223 self
224 }
225
226 pub fn channel(
230 mut self,
231 code: impl Into<String>,
232 location_code: impl Into<String>,
233 f: impl FnOnce(ChannelBuilder) -> ChannelBuilder,
234 ) -> Self {
235 let builder = f(ChannelBuilder::new(
236 code,
237 location_code,
238 self.latitude,
239 self.longitude,
240 self.elevation,
241 ));
242 self.channels.push(builder.build());
243 self
244 }
245
246 fn build(self) -> Station {
247 Station {
248 code: self.code,
249 description: self.description,
250 latitude: self.latitude,
251 longitude: self.longitude,
252 elevation: self.elevation,
253 site: Site {
254 name: self.site_name,
255 ..Default::default()
256 },
257 start_date: self.start_date,
258 end_date: self.end_date,
259 creation_date: self.creation_date,
260 channels: self.channels,
261 }
262 }
263}
264
265pub struct ChannelBuilder {
269 code: String,
270 location_code: String,
271 latitude: f64,
272 longitude: f64,
273 elevation: f64,
274 depth: f64,
275 azimuth: f64,
276 dip: f64,
277 sample_rate: f64,
278 start_date: Option<DateTime<Utc>>,
279 end_date: Option<DateTime<Utc>>,
280 sensor: Option<Equipment>,
281 data_logger: Option<Equipment>,
282 response: Option<Response>,
283}
284
285impl ChannelBuilder {
286 fn new(
287 code: impl Into<String>,
288 location_code: impl Into<String>,
289 station_lat: f64,
290 station_lon: f64,
291 station_elev: f64,
292 ) -> Self {
293 Self {
294 code: code.into(),
295 location_code: location_code.into(),
296 latitude: station_lat,
297 longitude: station_lon,
298 elevation: station_elev,
299 depth: 0.0,
300 azimuth: 0.0,
301 dip: 0.0,
302 sample_rate: 0.0,
303 start_date: None,
304 end_date: None,
305 sensor: None,
306 data_logger: None,
307 response: None,
308 }
309 }
310
311 pub fn latitude(mut self, lat: f64) -> Self {
312 self.latitude = lat;
313 self
314 }
315
316 pub fn longitude(mut self, lon: f64) -> Self {
317 self.longitude = lon;
318 self
319 }
320
321 pub fn elevation(mut self, elev: f64) -> Self {
322 self.elevation = elev;
323 self
324 }
325
326 pub fn depth(mut self, depth: f64) -> Self {
327 self.depth = depth;
328 self
329 }
330
331 pub fn azimuth(mut self, azimuth: f64) -> Self {
332 self.azimuth = azimuth;
333 self
334 }
335
336 pub fn dip(mut self, dip: f64) -> Self {
337 self.dip = dip;
338 self
339 }
340
341 pub fn sample_rate(mut self, rate: f64) -> Self {
342 self.sample_rate = rate;
343 self
344 }
345
346 pub fn start_date(mut self, date: DateTime<Utc>) -> Self {
347 self.start_date = Some(date);
348 self
349 }
350
351 pub fn end_date(mut self, date: DateTime<Utc>) -> Self {
352 self.end_date = Some(date);
353 self
354 }
355
356 pub fn sensor(mut self, sensor: Equipment) -> Self {
357 self.sensor = Some(sensor);
358 self
359 }
360
361 pub fn data_logger(mut self, dl: Equipment) -> Self {
362 self.data_logger = Some(dl);
363 self
364 }
365
366 pub fn response(mut self, response: Response) -> Self {
367 self.response = Some(response);
368 self
369 }
370
371 fn build(self) -> Channel {
372 Channel {
373 code: self.code,
374 location_code: self.location_code,
375 latitude: self.latitude,
376 longitude: self.longitude,
377 elevation: self.elevation,
378 depth: self.depth,
379 azimuth: self.azimuth,
380 dip: self.dip,
381 sample_rate: self.sample_rate,
382 start_date: self.start_date,
383 end_date: self.end_date,
384 sensor: self.sensor,
385 data_logger: self.data_logger,
386 response: self.response,
387 }
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394
395 #[test]
396 fn builder_minimal() {
397 let inv = Inventory::builder().source("Test").build();
398 assert_eq!(inv.source, "Test");
399 assert!(inv.networks.is_empty());
400 }
401
402 #[test]
403 fn builder_full() {
404 let inv = Inventory::builder()
405 .source("Pena Bumi")
406 .sender("stationxml-rs")
407 .network("XX", |net| {
408 net.description("Local Test Network")
409 .station("PBUMI", |sta| {
410 sta.latitude(-7.7714)
411 .longitude(110.3776)
412 .elevation(150.0)
413 .site_name("Yogyakarta Seismic Shelter")
414 .channel("SHZ", "00", |ch| {
415 ch.azimuth(0.0).dip(-90.0).sample_rate(100.0)
416 })
417 .channel("SHN", "00", |ch| {
418 ch.azimuth(0.0).dip(0.0).sample_rate(100.0)
419 })
420 .channel("SHE", "00", |ch| {
421 ch.azimuth(90.0).dip(0.0).sample_rate(100.0)
422 })
423 })
424 })
425 .build();
426
427 assert_eq!(inv.source, "Pena Bumi");
428 assert_eq!(inv.networks.len(), 1);
429 assert_eq!(inv.networks[0].code, "XX");
430
431 let sta = &inv.networks[0].stations[0];
432 assert_eq!(sta.code, "PBUMI");
433 assert_eq!(sta.channels.len(), 3);
434 assert_eq!(sta.site.name, "Yogyakarta Seismic Shelter");
435
436 let shz = &sta.channels[0];
438 assert_eq!(shz.code, "SHZ");
439 assert_eq!(shz.latitude, sta.latitude);
440 assert_eq!(shz.longitude, sta.longitude);
441 assert_eq!(shz.dip, -90.0);
442
443 let she = &sta.channels[2];
444 assert_eq!(she.code, "SHE");
445 assert_eq!(she.azimuth, 90.0);
446 assert_eq!(she.dip, 0.0);
447 }
448
449 #[test]
450 fn builder_with_sensor() {
451 let inv = Inventory::builder()
452 .source("Test")
453 .network("XX", |net| {
454 net.station("TEST", |sta| {
455 sta.latitude(0.0)
456 .longitude(0.0)
457 .elevation(0.0)
458 .site_name("Test")
459 .channel("SHZ", "00", |ch| {
460 ch.azimuth(0.0)
461 .dip(-90.0)
462 .sample_rate(100.0)
463 .sensor(Equipment {
464 equipment_type: Some("Geophone".into()),
465 model: Some("GS-11D".into()),
466 manufacturer: Some("Geospace".into()),
467 ..Default::default()
468 })
469 })
470 })
471 })
472 .build();
473
474 let sensor = inv.networks[0].stations[0].channels[0]
475 .sensor
476 .as_ref()
477 .unwrap();
478 assert_eq!(sensor.model.as_deref(), Some("GS-11D"));
479 }
480}