Skip to main content

epsg_utils/wkt2/
writer.rs

1//! `Display` implementations for emitting WKT2 text from parsed types.
2//!
3//! All types use the preferred WKT2 keywords (e.g. `PROJCRS` not `PROJECTEDCRS`,
4//! `ELLIPSOID` not `SPHEROID`, `METHOD` not `PROJECTION`).
5
6use std::fmt::{self, Display, Formatter, Write};
7
8use crate::crs::*;
9
10// ---------------------------------------------------------------------------
11// Helpers
12// ---------------------------------------------------------------------------
13
14/// Write a comma-separated sequence of items, each preceded by `,`.
15fn write_comma_items<T: Display>(f: &mut Formatter<'_>, items: &[T]) -> fmt::Result {
16    for item in items {
17        write!(f, ",{item}")?;
18    }
19    Ok(())
20}
21
22/// Write a quoted string.
23fn write_quoted(f: &mut Formatter<'_>, s: &str) -> fmt::Result {
24    write!(f, "\"{s}\"")
25}
26
27// ---------------------------------------------------------------------------
28// Top-level CRS
29// ---------------------------------------------------------------------------
30
31impl Display for Crs {
32    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
33        match self {
34            Crs::ProjectedCrs(crs) => crs.fmt(f),
35            Crs::GeogCrs(crs) => crs.fmt(f),
36            Crs::GeodCrs(crs) => crs.fmt(f),
37            Crs::VertCrs(crs) => crs.fmt(f),
38            Crs::CompoundCrs(crs) => crs.fmt(f),
39        }
40    }
41}
42
43impl Display for GeogCrs {
44    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
45        f.write_str("GEOGCRS[")?;
46        write_quoted(f, &self.name)?;
47
48        if let Some(ref dynamic) = self.dynamic {
49            write!(f, ",{dynamic}")?;
50        }
51
52        match &self.datum {
53            Datum::ReferenceFrame(rf) => {
54                write!(f, ",{rf}")?;
55                if let Some(ref pm) = rf.prime_meridian {
56                    write!(f, ",{pm}")?;
57                }
58            }
59            Datum::Ensemble(ens) => {
60                write!(f, ",{ens}")?;
61                if let Some(ref pm) = ens.prime_meridian {
62                    write!(f, ",{pm}")?;
63                }
64            }
65        }
66
67        write!(f, ",{}", self.coordinate_system)?;
68        write_comma_items(f, &self.usages)?;
69        write_comma_items(f, &self.identifiers)?;
70        if let Some(ref remark) = self.remark {
71            f.write_str(",REMARK[")?;
72            write_quoted(f, remark)?;
73            f.write_char(']')?;
74        }
75        f.write_char(']')
76    }
77}
78
79impl Display for GeodCrs {
80    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
81        f.write_str("GEODCRS[")?;
82        write_quoted(f, &self.name)?;
83
84        if let Some(ref dynamic) = self.dynamic {
85            write!(f, ",{dynamic}")?;
86        }
87
88        match &self.datum {
89            Datum::ReferenceFrame(rf) => {
90                write!(f, ",{rf}")?;
91                if let Some(ref pm) = rf.prime_meridian {
92                    write!(f, ",{pm}")?;
93                }
94            }
95            Datum::Ensemble(ens) => {
96                write!(f, ",{ens}")?;
97                if let Some(ref pm) = ens.prime_meridian {
98                    write!(f, ",{pm}")?;
99                }
100            }
101        }
102
103        write!(f, ",{}", self.coordinate_system)?;
104        write_comma_items(f, &self.usages)?;
105        write_comma_items(f, &self.identifiers)?;
106        if let Some(ref remark) = self.remark {
107            f.write_str(",REMARK[")?;
108            write_quoted(f, remark)?;
109            f.write_char(']')?;
110        }
111        f.write_char(']')
112    }
113}
114
115impl Display for VertCrs {
116    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
117        f.write_str("VERTCRS[")?;
118        write_quoted(f, &self.name)?;
119
120        match &self.source {
121            VertCrsSource::Datum { dynamic, datum } => {
122                if let Some(dynamic) = dynamic {
123                    write!(f, ",{dynamic}")?;
124                }
125                match datum {
126                    VerticalDatum::ReferenceFrame(rf) => write!(f, ",{rf}")?,
127                    VerticalDatum::Ensemble(ens) => write!(f, ",{ens}")?,
128                }
129            }
130            VertCrsSource::Derived {
131                base_vert_crs,
132                deriving_conversion,
133            } => {
134                write!(f, ",{base_vert_crs}")?;
135                f.write_str(",DERIVINGCONVERSION[")?;
136                write_quoted(f, &deriving_conversion.name)?;
137                write!(f, ",{}", deriving_conversion.method)?;
138                write_comma_items(f, &deriving_conversion.parameters)?;
139                write_comma_items(f, &deriving_conversion.identifiers)?;
140                f.write_char(']')?;
141            }
142        }
143
144        write!(f, ",{}", self.coordinate_system)?;
145        write_comma_items(f, &self.geoid_models)?;
146        write_comma_items(f, &self.usages)?;
147        write_comma_items(f, &self.identifiers)?;
148        if let Some(ref remark) = self.remark {
149            f.write_str(",REMARK[")?;
150            write_quoted(f, remark)?;
151            f.write_char(']')?;
152        }
153        f.write_char(']')
154    }
155}
156
157impl Display for BaseVertCrs {
158    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
159        f.write_str("BASEVERTCRS[")?;
160        write_quoted(f, &self.name)?;
161
162        if let Some(ref dynamic) = self.dynamic {
163            write!(f, ",{dynamic}")?;
164        }
165
166        match &self.datum {
167            VerticalDatum::ReferenceFrame(rf) => write!(f, ",{rf}")?,
168            VerticalDatum::Ensemble(ens) => write!(f, ",{ens}")?,
169        }
170
171        write_comma_items(f, &self.identifiers)?;
172        f.write_char(']')
173    }
174}
175
176impl Display for VerticalReferenceFrame {
177    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
178        f.write_str("VDATUM[")?;
179        write_quoted(f, &self.name)?;
180        if let Some(ref anchor) = self.anchor {
181            f.write_str(",ANCHOR[")?;
182            write_quoted(f, anchor)?;
183            f.write_char(']')?;
184        }
185        if let Some(epoch) = self.anchor_epoch {
186            write!(f, ",ANCHOREPOCH[{epoch}]")?;
187        }
188        write_comma_items(f, &self.identifiers)?;
189        f.write_char(']')
190    }
191}
192
193impl Display for GeoidModel {
194    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
195        f.write_str("GEOIDMODEL[")?;
196        write_quoted(f, &self.name)?;
197        write_comma_items(f, &self.identifiers)?;
198        f.write_char(']')
199    }
200}
201
202impl Display for CompoundCrs {
203    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
204        f.write_str("COMPOUNDCRS[")?;
205        write_quoted(f, &self.name)?;
206        for component in &self.components {
207            write!(f, ",{component}")?;
208        }
209        write_comma_items(f, &self.usages)?;
210        write_comma_items(f, &self.identifiers)?;
211        if let Some(ref remark) = self.remark {
212            f.write_str(",REMARK[")?;
213            write_quoted(f, remark)?;
214            f.write_char(']')?;
215        }
216        f.write_char(']')
217    }
218}
219
220impl Display for SingleCrs {
221    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
222        match self {
223            SingleCrs::ProjectedCrs(crs) => crs.fmt(f),
224            SingleCrs::GeogCrs(crs) => crs.fmt(f),
225            SingleCrs::GeodCrs(crs) => crs.fmt(f),
226            SingleCrs::VertCrs(crs) => crs.fmt(f),
227            SingleCrs::Other(raw) => f.write_str(raw),
228        }
229    }
230}
231
232impl Display for ProjectedCrs {
233    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
234        f.write_str("PROJCRS[")?;
235        write_quoted(f, &self.name)?;
236        write!(f, ",{}", self.base_geodetic_crs)?;
237        write!(f, ",{}", self.map_projection)?;
238        write!(f, ",{}", self.coordinate_system)?;
239        write_comma_items(f, &self.usages)?;
240        write_comma_items(f, &self.identifiers)?;
241        if let Some(ref remark) = self.remark {
242            f.write_str(",REMARK[")?;
243            write_quoted(f, remark)?;
244            f.write_char(']')?;
245        }
246        f.write_char(']')
247    }
248}
249
250// ---------------------------------------------------------------------------
251// Base geodetic CRS
252// ---------------------------------------------------------------------------
253
254impl Display for BaseGeodeticCrsKeyword {
255    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
256        f.write_str(match self {
257            BaseGeodeticCrsKeyword::BaseGeodCrs => "BASEGEODCRS",
258            BaseGeodeticCrsKeyword::BaseGeogCrs => "BASEGEOGCRS",
259        })
260    }
261}
262
263impl Display for BaseGeodeticCrs {
264    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
265        write!(f, "{}[", self.keyword)?;
266        write_quoted(f, &self.name)?;
267
268        if let Some(ref dynamic) = self.dynamic {
269            write!(f, ",{dynamic}")?;
270        }
271
272        match &self.datum {
273            Datum::ReferenceFrame(rf) => {
274                write!(f, ",{rf}")?;
275                if let Some(ref pm) = rf.prime_meridian {
276                    write!(f, ",{pm}")?;
277                }
278            }
279            Datum::Ensemble(ens) => {
280                write!(f, ",{ens}")?;
281                if let Some(ref pm) = ens.prime_meridian {
282                    write!(f, ",{pm}")?;
283                }
284            }
285        }
286
287        if let Some(ref unit) = self.ellipsoidal_cs_unit {
288            write!(f, ",{unit}")?;
289        }
290        write_comma_items(f, &self.identifiers)?;
291        f.write_char(']')
292    }
293}
294
295// ---------------------------------------------------------------------------
296// Dynamic CRS
297// ---------------------------------------------------------------------------
298
299impl Display for DynamicCrs {
300    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
301        write!(f, "DYNAMIC[FRAMEEPOCH[{}]", self.frame_reference_epoch)?;
302        if let Some(ref model) = self.deformation_model {
303            write!(f, ",{model}")?;
304        }
305        f.write_char(']')
306    }
307}
308
309impl Display for DeformationModel {
310    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
311        f.write_str("MODEL[")?;
312        write_quoted(f, &self.name)?;
313        write_comma_items(f, &self.identifiers)?;
314        f.write_char(']')
315    }
316}
317
318// ---------------------------------------------------------------------------
319// Datum
320// ---------------------------------------------------------------------------
321
322impl Display for GeodeticReferenceFrame {
323    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
324        f.write_str("DATUM[")?;
325        write_quoted(f, &self.name)?;
326        write!(f, ",{}", self.ellipsoid)?;
327        if let Some(ref anchor) = self.anchor {
328            f.write_str(",ANCHOR[")?;
329            write_quoted(f, anchor)?;
330            f.write_char(']')?;
331        }
332        if let Some(epoch) = self.anchor_epoch {
333            write!(f, ",ANCHOREPOCH[{epoch}]")?;
334        }
335        write_comma_items(f, &self.identifiers)?;
336        // prime_meridian is written by the parent (BaseGeodeticCrs) as a sibling
337        f.write_char(']')
338    }
339}
340
341impl Display for DatumEnsemble {
342    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
343        f.write_str("ENSEMBLE[")?;
344        write_quoted(f, &self.name)?;
345        write_comma_items(f, &self.members)?;
346        if let Some(ref ellipsoid) = self.ellipsoid {
347            write!(f, ",{ellipsoid}")?;
348        }
349        write!(f, ",ENSEMBLEACCURACY[{}]", self.accuracy)?;
350        write_comma_items(f, &self.identifiers)?;
351        // prime_meridian is written by the parent (BaseGeodeticCrs) as a sibling
352        f.write_char(']')
353    }
354}
355
356impl Display for EnsembleMember {
357    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
358        f.write_str("MEMBER[")?;
359        write_quoted(f, &self.name)?;
360        write_comma_items(f, &self.identifiers)?;
361        f.write_char(']')
362    }
363}
364
365// ---------------------------------------------------------------------------
366// Ellipsoid & Prime Meridian
367// ---------------------------------------------------------------------------
368
369impl Display for Ellipsoid {
370    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
371        f.write_str("ELLIPSOID[")?;
372        write_quoted(f, &self.name)?;
373        write!(f, ",{},{}", self.semi_major_axis, self.inverse_flattening)?;
374        if let Some(ref unit) = self.unit {
375            write!(f, ",{unit}")?;
376        }
377        write_comma_items(f, &self.identifiers)?;
378        f.write_char(']')
379    }
380}
381
382impl Display for PrimeMeridian {
383    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
384        f.write_str("PRIMEM[")?;
385        write_quoted(f, &self.name)?;
386        write!(f, ",{}", self.irm_longitude)?;
387        if let Some(ref unit) = self.unit {
388            write!(f, ",{unit}")?;
389        }
390        write_comma_items(f, &self.identifiers)?;
391        f.write_char(']')
392    }
393}
394
395// ---------------------------------------------------------------------------
396// Map Projection
397// ---------------------------------------------------------------------------
398
399impl Display for MapProjection {
400    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
401        f.write_str("CONVERSION[")?;
402        write_quoted(f, &self.name)?;
403        write!(f, ",{}", self.method)?;
404        write_comma_items(f, &self.parameters)?;
405        write_comma_items(f, &self.identifiers)?;
406        f.write_char(']')
407    }
408}
409
410impl Display for MapProjectionMethod {
411    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
412        f.write_str("METHOD[")?;
413        write_quoted(f, &self.name)?;
414        write_comma_items(f, &self.identifiers)?;
415        f.write_char(']')
416    }
417}
418
419impl Display for MapProjectionParameter {
420    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
421        f.write_str("PARAMETER[")?;
422        write_quoted(f, &self.name)?;
423        write!(f, ",{}", self.value)?;
424        if let Some(ref unit) = self.unit {
425            write!(f, ",{unit}")?;
426        }
427        write_comma_items(f, &self.identifiers)?;
428        f.write_char(']')
429    }
430}
431
432// ---------------------------------------------------------------------------
433// Coordinate System
434// ---------------------------------------------------------------------------
435
436impl Display for CoordinateSystem {
437    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
438        write!(f, "CS[{},{}", self.cs_type, self.dimension)?;
439        write_comma_items(f, &self.identifiers)?;
440        f.write_char(']')?;
441        // axes and cs_unit are siblings after CS[...]
442        write_comma_items(f, &self.axes)?;
443        if let Some(ref unit) = self.cs_unit {
444            write!(f, ",{unit}")?;
445        }
446        Ok(())
447    }
448}
449
450impl Display for CsType {
451    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
452        f.write_str(match self {
453            CsType::Affine => "affine",
454            CsType::Cartesian => "Cartesian",
455            CsType::Cylindrical => "cylindrical",
456            CsType::Ellipsoidal => "ellipsoidal",
457            CsType::Linear => "linear",
458            CsType::Parametric => "parametric",
459            CsType::Polar => "polar",
460            CsType::Spherical => "spherical",
461            CsType::Vertical => "vertical",
462            CsType::TemporalCount => "temporalCount",
463            CsType::TemporalMeasure => "temporalMeasure",
464            CsType::Ordinal => "ordinal",
465            CsType::TemporalDateTime => "temporalDateTime",
466        })
467    }
468}
469
470impl Display for Axis {
471    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
472        f.write_str("AXIS[")?;
473        write_quoted(f, &self.name_abbrev)?;
474        write!(f, ",{}", self.direction)?;
475        if let Some(ref meridian) = self.meridian {
476            write!(f, ",{meridian}")?;
477        }
478        if let Some(bearing) = self.bearing {
479            write!(f, ",BEARING[{bearing}]")?;
480        }
481        if let Some(order) = self.order {
482            write!(f, ",ORDER[{order}]")?;
483        }
484        if let Some(ref unit) = self.unit {
485            write!(f, ",{unit}")?;
486        }
487        if let Some(min) = self.axis_min_value {
488            write!(f, ",AXISMINVALUE[{min}]")?;
489        }
490        if let Some(max) = self.axis_max_value {
491            write!(f, ",AXISMAXVALUE[{max}]")?;
492        }
493        if let Some(rm) = self.range_meaning {
494            write!(f, ",{rm}")?;
495        }
496        write_comma_items(f, &self.identifiers)?;
497        f.write_char(']')
498    }
499}
500
501impl Display for Meridian {
502    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
503        write!(f, "MERIDIAN[{},{}]", self.value, self.unit)
504    }
505}
506
507impl Display for RangeMeaning {
508    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
509        f.write_str(match self {
510            RangeMeaning::Exact => "RANGEMEANING[exact]",
511            RangeMeaning::Wraparound => "RANGEMEANING[wraparound]",
512        })
513    }
514}
515
516// ---------------------------------------------------------------------------
517// Unit
518// ---------------------------------------------------------------------------
519
520impl Display for UnitKeyword {
521    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
522        f.write_str(match self {
523            UnitKeyword::AngleUnit => "ANGLEUNIT",
524            UnitKeyword::LengthUnit => "LENGTHUNIT",
525            UnitKeyword::ParametricUnit => "PARAMETRICUNIT",
526            UnitKeyword::ScaleUnit => "SCALEUNIT",
527            UnitKeyword::TimeUnit => "TIMEUNIT",
528            UnitKeyword::Unit => "UNIT",
529        })
530    }
531}
532
533impl Display for Unit {
534    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
535        write!(f, "{}[", self.keyword)?;
536        write_quoted(f, &self.name)?;
537        if let Some(factor) = self.conversion_factor {
538            write!(f, ",{factor}")?;
539        }
540        write_comma_items(f, &self.identifiers)?;
541        f.write_char(']')
542    }
543}
544
545// ---------------------------------------------------------------------------
546// Identifier
547// ---------------------------------------------------------------------------
548
549impl Display for AuthorityId {
550    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
551        match self {
552            AuthorityId::Number(n) => write!(f, "{n}"),
553            AuthorityId::Text(s) => write_quoted(f, s),
554        }
555    }
556}
557
558impl Display for Identifier {
559    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
560        f.write_str("ID[")?;
561        write_quoted(f, &self.authority_name)?;
562        write!(f, ",{}", self.authority_unique_id)?;
563        if let Some(ref version) = self.version {
564            write!(f, ",{version}")?;
565        }
566        if let Some(ref citation) = self.citation {
567            f.write_str(",CITATION[")?;
568            write_quoted(f, citation)?;
569            f.write_char(']')?;
570        }
571        if let Some(ref uri) = self.uri {
572            f.write_str(",URI[")?;
573            write_quoted(f, uri)?;
574            f.write_char(']')?;
575        }
576        f.write_char(']')
577    }
578}
579
580// ---------------------------------------------------------------------------
581// Usage & Extent
582// ---------------------------------------------------------------------------
583
584impl Display for Usage {
585    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
586        f.write_str("USAGE[SCOPE[")?;
587        write_quoted(f, &self.scope)?;
588        f.write_char(']')?;
589        if let Some(ref area) = self.area {
590            f.write_str(",AREA[")?;
591            write_quoted(f, area)?;
592            f.write_char(']')?;
593        }
594        if let Some(ref bbox) = self.bbox {
595            write!(f, ",{bbox}")?;
596        }
597        if let Some(ref ve) = self.vertical_extent {
598            write!(f, ",{ve}")?;
599        }
600        if let Some(ref te) = self.temporal_extent {
601            write!(f, ",{te}")?;
602        }
603        f.write_char(']')
604    }
605}
606
607impl Display for BBox {
608    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
609        write!(
610            f,
611            "BBOX[{},{},{},{}]",
612            self.lower_left_latitude,
613            self.lower_left_longitude,
614            self.upper_right_latitude,
615            self.upper_right_longitude
616        )
617    }
618}
619
620impl Display for VerticalExtent {
621    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
622        write!(
623            f,
624            "VERTICALEXTENT[{},{}",
625            self.minimum_height, self.maximum_height
626        )?;
627        if let Some(ref unit) = self.unit {
628            write!(f, ",{unit}")?;
629        }
630        f.write_char(']')
631    }
632}
633
634impl Display for TemporalExtent {
635    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
636        // If the values look like dates (contain digits/hyphens), write unquoted;
637        // otherwise write quoted.
638        write!(f, "TIMEEXTENT[")?;
639        write_temporal_value(f, &self.start)?;
640        f.write_char(',')?;
641        write_temporal_value(f, &self.end)?;
642        f.write_char(']')
643    }
644}
645
646fn write_temporal_value(f: &mut Formatter<'_>, value: &str) -> fmt::Result {
647    // If it looks like a date/time (starts with a digit), write unquoted
648    if value.starts_with(|c: char| c.is_ascii_digit()) {
649        f.write_str(value)
650    } else {
651        write_quoted(f, value)
652    }
653}