gistools/proj/parse/
wkt.rs

1// https://docs.ogc.org/is/18-010r7/18-010r7.html
2use super::{
3    Axis, Conversion, CoordinateSystem, Datum, DatumEnsemble, DatumEnsembleMember, Ellipsoid,
4    EngineeringDatum, GeodeticCRS, GeodeticReferenceFrame, Id, Meridian, Method, ObjectUsage,
5    ParameterValue, ParametricDatum, PrimeMeridian, ProjBBox, ProjJSON, ProjectedCRS,
6    TemporalDatum, TemporalExtent, ToProjJSON, Unit, UnitObject, UnitType,
7    ValueInDegreeOrValueAndUnit, ValueInMetreOrValueAndUnit, VerticalExtent,
8    VerticalReferenceFrame,
9};
10use crate::{
11    parsers::{WKTParser, WKTValue, parse_wkt_object},
12    proj::AxisDirection,
13};
14use alloc::format;
15
16impl WKTParser for ObjectUsage {
17    fn from_wkt(val: &WKTValue) -> Self {
18        let mut usage = ObjectUsage::default();
19        if let WKTValue::Array(arr) = val {
20            if !arr.is_empty()
21                && arr[0].to_string() == "SCOPE"
22                && let WKTValue::Array(arr) = &arr[1]
23            {
24                usage.scope = arr.first().map(|s| s.to_string()).unwrap_or_default();
25            }
26            if arr.len() >= 2 {
27                handle_common_fields(&mut usage, arr, 2);
28            }
29        }
30        usage
31    }
32}
33
34impl WKTParser for ProjBBox {
35    fn from_wkt(val: &WKTValue) -> Self {
36        let mut bbox = ProjBBox::default();
37        if let WKTValue::Array(arr) = val
38            && arr.len() >= 4
39        {
40            bbox.south_latitude = arr[0].to_float();
41            bbox.west_longitude = arr[1].to_float();
42            bbox.north_latitude = arr[2].to_float();
43            bbox.east_longitude = arr[3].to_float();
44        }
45        bbox
46    }
47}
48
49impl WKTParser for VerticalExtent {
50    fn from_wkt(val: &WKTValue) -> Self {
51        let mut ve = VerticalExtent::default();
52        if let WKTValue::Array(arr) = val
53            && arr.len() >= 2
54        {
55            ve.minimum = arr[0].to_float();
56            ve.maximum = arr[1].to_float();
57            if arr.len() >= 4 && arr[2].to_string() == "LENGTHUNIT" {
58                ve.unit = Unit::from_wkt(&arr[3]);
59                ve.unit.set_unit_type(UnitType::LinearUnit);
60            }
61        }
62        ve
63    }
64}
65
66impl WKTParser for Unit {
67    /// Creates a new LengthUnit from a WKTValue
68    fn from_wkt(unit_xml: &WKTValue) -> Self {
69        let mut unit = UnitObject::default();
70        if let WKTValue::Array(arr) = unit_xml {
71            if let Some(name) = arr.first() {
72                unit.name = name.to_string();
73            }
74            if let Some(conversion_factor) = arr.get(1) {
75                unit.conversion_factor = Some(conversion_factor.to_float());
76            }
77            handle_common_fields(&mut unit, arr, 2);
78        }
79        Unit::UnitObject(unit)
80    }
81}
82
83impl WKTParser for TemporalExtent {
84    fn from_wkt(val: &WKTValue) -> Self {
85        let mut te = TemporalExtent::default();
86        if let WKTValue::Array(arr) = val
87            && arr.len() >= 2
88        {
89            te.start = arr.first().map(|v| v.to_string()).unwrap_or_default();
90            te.end = arr.get(1).map(|v| v.to_string()).unwrap_or_default();
91        }
92        te
93    }
94}
95
96impl WKTParser for Id {
97    fn from_wkt(val: &WKTValue) -> Self {
98        let mut id = Id::default();
99        if let WKTValue::Array(arr) = val
100            && arr.len() >= 2
101        {
102            id.authority = arr[0].to_string();
103            id.code = arr[1].to_string().into();
104
105            let mut i = 2;
106            while i < arr.len() {
107                match arr[i].to_string().as_str() {
108                    "CITATION" => {
109                        if let WKTValue::Array(arr) = &arr[i + 1] {
110                            id.authority_citation =
111                                Some(arr.first().map(|s| s.to_string()).unwrap_or_default());
112                        }
113                        i += 2;
114                    }
115                    "URI" => {
116                        if let WKTValue::Array(arr) = &arr[i + 1] {
117                            id.uri = Some(arr.first().map(|s| s.to_string()).unwrap_or_default());
118                        }
119                        i += 2;
120                    }
121                    other => {
122                        id.version = Some(other.into());
123                        i += 1;
124                    }
125                }
126            }
127        }
128        id
129    }
130}
131
132impl WKTParser for Ellipsoid {
133    fn from_wkt(val: &WKTValue) -> Self {
134        let mut ellipsoid = Ellipsoid::default();
135        let mut unit = Unit::default();
136        let mut semi_major_axis = 0.0;
137        let mut inverse_flattening = 0.0;
138
139        if let WKTValue::Array(arr) = val
140            && arr.len() >= 3
141        {
142            ellipsoid.name = arr[0].to_string();
143            semi_major_axis = arr[1].to_float();
144            inverse_flattening = arr[2].to_float();
145
146            // get unit
147            handle_common_fields(&mut unit, arr, 3);
148            // get ID
149            handle_common_fields(&mut ellipsoid, arr, 3);
150        }
151        // update semi-major axis and inverse-flattening, but if unit exists, build it correctly
152        if let Unit::UnitObject(_) = unit {
153            ellipsoid.semi_major_axis =
154                Some(ValueInMetreOrValueAndUnit::from_unit(unit.clone(), semi_major_axis));
155            ellipsoid.inverse_flattening =
156                Some(ValueInMetreOrValueAndUnit::from_unit(unit, inverse_flattening));
157        } else {
158            if semi_major_axis != 0.0 {
159                ellipsoid.semi_major_axis = Some(semi_major_axis.into());
160            }
161            if inverse_flattening != 0.0 {
162                ellipsoid.inverse_flattening = Some(inverse_flattening.into());
163            }
164        }
165        ellipsoid
166    }
167}
168
169impl WKTParser for ParameterValue {
170    fn from_wkt(wkt: &WKTValue) -> Self {
171        let mut parameter_value = ParameterValue::default();
172
173        if let WKTValue::Array(arr) = wkt {
174            if arr.len() >= 2 {
175                // FIRST value is the name
176                parameter_value.name = arr[0].to_string().to_lowercase().replace(" ", "_");
177                parameter_value.value = arr[1].to_string().into();
178            }
179            handle_common_fields(&mut parameter_value, arr, 2);
180        }
181        parameter_value
182    }
183}
184
185impl WKTParser for CoordinateSystem {
186    fn from_wkt(val: &WKTValue) -> Self {
187        let mut cs = CoordinateSystem::default();
188        if let WKTValue::Array(arr) = val
189            && !arr.is_empty()
190        {
191            cs.subtype =
192                serde_json::from_str(&format!("\"{}\"", arr[0].to_string())).unwrap_or_default();
193        }
194        cs
195    }
196}
197
198impl WKTParser for Axis {
199    fn from_wkt(val: &WKTValue) -> Self {
200        let mut axis = Axis::default();
201        if let WKTValue::Array(arr) = val
202            && arr.len() >= 2
203        {
204            axis.abbreviation = arr[0].to_string();
205            axis.name = axis.abbreviation.clone();
206            axis.direction = AxisDirection::from(arr[1].to_string());
207            handle_common_fields(&mut axis, arr, 2);
208            // NOTE: BEARING, ORDER, and ANGLEUNIT exist, but add no value in modern WKT
209        }
210        axis.adjust_if_needed();
211        axis
212    }
213}
214
215impl WKTParser for DatumEnsembleMember {
216    fn from_wkt(val: &WKTValue) -> Self {
217        let mut datum_ensemble_member = DatumEnsembleMember::default();
218        if let WKTValue::Array(arr) = val {
219            if let Some(name) = arr.first() {
220                datum_ensemble_member.name = name.to_string();
221            }
222            handle_common_fields(&mut datum_ensemble_member, arr, 1);
223        }
224        datum_ensemble_member
225    }
226}
227
228impl WKTParser for Method {
229    fn from_wkt(val: &WKTValue) -> Self {
230        let mut method = Method::default();
231        if let WKTValue::Array(arr) = val {
232            if let Some(name) = arr.first() {
233                method.name = name.to_string();
234            }
235            handle_common_fields(&mut method, arr, 1);
236        }
237        method
238    }
239}
240
241impl WKTParser for DatumEnsemble {
242    fn from_wkt(val: &WKTValue) -> Self {
243        let mut ensemble = DatumEnsemble::default();
244        if let WKTValue::Array(arr) = val {
245            if let Some(name) = arr.first() {
246                ensemble.name = name.to_string();
247            }
248            handle_common_fields(&mut ensemble, arr, 1);
249        }
250        ensemble
251    }
252}
253
254impl WKTParser for PrimeMeridian {
255    fn from_wkt(val: &WKTValue) -> Self {
256        let mut pm = PrimeMeridian::default();
257        if let WKTValue::Array(arr) = val {
258            if let Some(name) = arr.first() {
259                pm.name = name.to_string();
260            }
261            if let Some(lon) = arr.get(1) {
262                pm.longitude =
263                    ValueInDegreeOrValueAndUnit::from_unit(Unit::new_deg(), lon.to_float());
264            }
265            handle_common_fields(&mut pm, arr, 2);
266        }
267        pm
268    }
269}
270
271impl WKTParser for Meridian {
272    fn from_wkt(val: &WKTValue) -> Self {
273        let mut meridian = Meridian::default();
274        if let WKTValue::Array(arr) = val {
275            let mut unit = Unit::default();
276            if let Some(unit_xml) = arr.get(1) {
277                unit = Unit::from_wkt(unit_xml);
278            }
279            if let Some(lon) = arr.first() {
280                meridian.longitude = ValueInDegreeOrValueAndUnit::from_unit(unit, lon.to_float());
281            }
282            handle_common_fields(&mut meridian, arr, 2);
283        }
284        meridian
285    }
286}
287
288impl WKTParser for GeodeticReferenceFrame {
289    fn from_wkt(val: &WKTValue) -> Self {
290        let mut geodetic_reference_frame = GeodeticReferenceFrame::default();
291        if let WKTValue::Array(arr) = val {
292            if let Some(name) = arr.first() {
293                geodetic_reference_frame.name = name.to_string();
294            }
295            handle_common_fields(&mut geodetic_reference_frame, arr, 1);
296        }
297        geodetic_reference_frame
298    }
299}
300
301impl WKTParser for VerticalReferenceFrame {
302    fn from_wkt(val: &WKTValue) -> Self {
303        let mut vertical_reference_frame = VerticalReferenceFrame::default();
304        if let WKTValue::Array(arr) = val {
305            if let Some(name) = arr.first() {
306                vertical_reference_frame.name = name.to_string();
307            }
308            handle_common_fields(&mut vertical_reference_frame, arr, 1);
309        }
310        vertical_reference_frame
311    }
312}
313
314impl WKTParser for TemporalDatum {
315    fn from_wkt(val: &WKTValue) -> Self {
316        // TODO: CALENDAR and TIME_ORIGIN exist, but add no value in modern WKT.
317        // Should be handled as "common fields" though.
318        let mut temporal_datum = TemporalDatum::default();
319        if let WKTValue::Array(arr) = val {
320            if let Some(name) = arr.first() {
321                temporal_datum.name = name.to_string();
322            }
323            handle_common_fields(&mut temporal_datum, arr, 1);
324        }
325        temporal_datum
326    }
327}
328
329impl WKTParser for EngineeringDatum {
330    fn from_wkt(val: &WKTValue) -> Self {
331        let mut engineering_datum = EngineeringDatum::default();
332        if let WKTValue::Array(arr) = val {
333            if let Some(name) = arr.first() {
334                engineering_datum.name = name.to_string();
335            }
336            handle_common_fields(&mut engineering_datum, arr, 1);
337        }
338        engineering_datum
339    }
340}
341
342impl WKTParser for ParametricDatum {
343    fn from_wkt(val: &WKTValue) -> Self {
344        let mut parametric_datum = ParametricDatum::default();
345        if let WKTValue::Array(arr) = val {
346            if let Some(name) = arr.first() {
347                parametric_datum.name = name.to_string();
348            }
349            handle_common_fields(&mut parametric_datum, arr, 1);
350        }
351        parametric_datum
352    }
353}
354
355impl WKTParser for Conversion {
356    fn from_wkt(val: &WKTValue) -> Self {
357        let mut conversion = Conversion::default();
358        if let WKTValue::Array(arr) = val {
359            if let Some(name) = arr.first() {
360                conversion.name = name.to_string();
361            }
362            handle_common_fields(&mut conversion, arr, 1);
363        }
364        conversion
365    }
366}
367
368impl WKTParser for GeodeticCRS {
369    fn from_wkt(val: &WKTValue) -> Self {
370        let mut geodetic_crs = GeodeticCRS::default();
371        if let WKTValue::Array(arr) = val {
372            if let Some(name) = arr.first() {
373                geodetic_crs.name = name.to_string();
374            }
375            handle_common_fields(&mut geodetic_crs, arr, 1);
376        }
377        geodetic_crs
378    }
379}
380
381impl WKTParser for ProjectedCRS {
382    fn from_wkt(val: &WKTValue) -> Self {
383        let mut projected_crs = ProjectedCRS::default();
384        if let WKTValue::Array(arr) = val {
385            if let Some(name) = arr.first() {
386                projected_crs.name = name.to_string();
387            }
388            handle_common_fields(&mut projected_crs, arr, 1);
389        }
390        projected_crs
391    }
392}
393
394const TOP_LEVEL_PROJ_KEYWORDS: [&str; 27] = [
395    "BoundCRS",
396    "PROJCRS",
397    "PROJCS",
398    "BASEPROJCRS",
399    "PROJECTEDCRS",
400    "COORDINATEOPERATION",
401    "VERTCRS",
402    "VERTICALCRS",
403    "VERT_CS",
404    "GEOGCS",
405    "GEOGCRS",
406    "GEODCRS",
407    "BASEGEODCRS",
408    "BASEGEOGCRS",
409    "GEODETICCRS",
410    "GEOGRAPHICCRS",
411    "COMPOUNDCRS",
412    "COMPD_CS",
413    "CONCATENATEDOPERATION",
414    "DERIVEDPROJECTED",
415    "EngineeringCRS",
416    "ENGCRS",
417    "LOCAL_CS",
418    "ParametricCRS",
419    "POINTMOTIONOPERATION",
420    "TemporalCRS",
421    "TIMECRS",
422];
423
424impl ProjJSON {
425    /// Convert a string to WKT object and then to ProjJSON object
426    pub fn parse_wkt(val: &str) -> Self {
427        let wkt = parse_wkt_object(val);
428        Self::from_wkt(&wkt)
429    }
430}
431impl WKTParser for ProjJSON {
432    fn from_wkt(val: &WKTValue) -> Self {
433        let mut proj_json = ProjJSON::default();
434        if let WKTValue::Array(arr) = val {
435            // ensure the name at the beginning is one of the top level types
436            if let Some(name) = arr.first() {
437                let name = name.to_string();
438                if !TOP_LEVEL_PROJ_KEYWORDS.contains(&name.as_str()) {
439                    panic!("Expected one of {}", TOP_LEVEL_PROJ_KEYWORDS.join(", "));
440                }
441            }
442            handle_common_fields(&mut proj_json, arr, 0);
443        }
444        proj_json
445    }
446}
447
448/// Helper function to handle insert common fields into various ProjJSON objects
449fn handle_common_fields<T: ToProjJSON>(res: &mut T, arr: &[WKTValue], start_index: usize) {
450    let mut i = start_index;
451    while i < arr.len() {
452        if let Some(WKTValue::String(item_keyword)) = arr.get(i) {
453            let key = item_keyword.as_str();
454            match key {
455                "ID" | "AUTHORITY" => {
456                    res.set_id(Id::from_wkt(&arr[i + 1]));
457                    i += 1;
458                }
459                "UNIT" | "LENGTHUNIT" | "ANGLEUNIT" | "SCALEUNIT" | "TIMEUNIT"
460                | "PARAMETRICUNIT" => {
461                    let mut unit = Unit::from_wkt(&arr[i + 1]);
462                    unit.set_unit_type(key.into());
463                    res.set_unit(unit);
464                    i += 1;
465                }
466                "CS" => {
467                    res.set_coordinate_system(CoordinateSystem::from_wkt(&arr[i + 1]));
468                    i += 1;
469                }
470                "AXIS" => {
471                    res.set_axis(Axis::from_wkt(&arr[i + 1]));
472                    i += 1;
473                }
474                "MEMBERS" => {
475                    res.set_member(DatumEnsembleMember::from_wkt(&arr[i + 1]));
476                    i += 1;
477                }
478                "ELLIPSOID" | "SPHEROID" => {
479                    res.set_ellipsoid(Ellipsoid::from_wkt(&arr[i + 1]));
480                    i += 1;
481                }
482                "ENSEMBLEACCURACY" | "OPERATIONACCURACY" => {
483                    res.set_accuracy(arr[i + 1].to_string());
484                    i += 1;
485                }
486                "EPOCH" | "ANCHOREPOCH" | "COORDEPOCH" => {
487                    let WKTValue::Array(arr) = &arr[i + 1] else {
488                        continue;
489                    };
490                    let epoch = arr.first().map(|s| s.to_float()).unwrap_or_default();
491                    res.set_epoch(epoch);
492                    i += 1;
493                }
494                "FRAMEEPOCH" => {
495                    res.set_frame_epoch(arr[i + 1].to_float());
496                    i += 1;
497                }
498                "ENSEMBLE" => {
499                    res.set_ensemble(DatumEnsemble::from_wkt(&arr[i + 1]));
500                    i += 1;
501                }
502                "DATUM" | "GEODETICDATUM" | "TRF" => {
503                    res.set_datum(Datum::GeodeticReferenceFrame(GeodeticReferenceFrame::from_wkt(
504                        &arr[i + 1],
505                    )));
506                    i += 1;
507                }
508                "VDATUM" | "VRF" | "VERTICALDATUM" => {
509                    res.set_datum(Datum::VerticalReferenceFrame(VerticalReferenceFrame::from_wkt(
510                        &arr[i + 1],
511                    )));
512                    i += 1;
513                }
514                "TDATUM" | "TIMEDATUM" => {
515                    res.set_datum(Datum::TemporalDatum(TemporalDatum::from_wkt(&arr[i + 1])));
516                    i += 1;
517                }
518                "EDATUM" | "ENGINEERINGDATUM" => {
519                    res.set_datum(Datum::EngineeringDatum(EngineeringDatum::from_wkt(&arr[i + 1])));
520                    i += 1;
521                }
522                "PDATUM" | "PARAMETRICDATUM" => {
523                    res.set_datum(Datum::ParametricDatum(ParametricDatum::from_wkt(&arr[i + 1])));
524                    i += 1;
525                }
526                "METHOD" => {
527                    res.set_method(Method::from_wkt(&arr[i + 1]));
528                    i += 1;
529                }
530                "PARAMETER" | "PARAMETERFILE" => {
531                    let mut param = ParameterValue::from_wkt(&arr[i + 1]);
532                    param.is_file = key == "PARAMETERFILE";
533                    res.set_parameter(param);
534                    i += 1;
535                }
536                "PRIMEM" | "PRIMEMERIDIAN" => {
537                    res.set_prime_meridian(PrimeMeridian::from_wkt(&arr[i + 1]));
538                    i += 1;
539                }
540                "MERIDIAN" => {
541                    res.set_meridian(Meridian::from_wkt(&arr[i + 1]));
542                    i += 1;
543                }
544                "TIMEEXTENT" => {
545                    res.set_temporal_extent(TemporalExtent::from_wkt(&arr[i + 1]));
546                    i += 1;
547                }
548                "VERTICALEXTENT" => {
549                    res.set_vertical_extent(VerticalExtent::from_wkt(&arr[i + 1]));
550                    i += 1;
551                }
552                "BBOX" => {
553                    res.set_bbox(ProjBBox::from_wkt(&arr[i + 1]));
554                    i += 1;
555                }
556                "AREA" => {
557                    if let WKTValue::Array(arr) = &arr[i + 1] {
558                        res.set_area(arr.first().map(|s| s.to_string()));
559                        i += 1;
560                    }
561                }
562                "DERIVINGCONVERSION" | "CONVERSION" => {
563                    res.set_conversion(Conversion::from_wkt(&arr[i + 1]));
564                    i += 1;
565                }
566                "GEOGCS" | "GEOGCRS" | "GEOGRAPHICCRS" | "GEODCRS" | "GEODETICCRS"
567                | "BASEGEODCRS" | "BASEGEOGCRS" => {
568                    res.set_geodetic_crs(GeodeticCRS::from_wkt(&arr[i + 1]));
569                    i += 1;
570                }
571                "PROJCRS" | "PROJECTEDCRS" | "PROJCS" | "BASEPROJCRS" => {
572                    res.set_projected_crs(ProjectedCRS::from_wkt(&arr[i + 1]));
573                    i += 1;
574                }
575                "ANCHOR" => {
576                    let WKTValue::Array(arr) = &arr[i + 1] else {
577                        continue;
578                    };
579                    let anchor = arr.first().map(|s| s.to_string()).unwrap_or_default();
580                    res.set_anchor(anchor);
581                    i += 1;
582                }
583                "USAGE" => {
584                    res.set_usage(ObjectUsage::from_wkt(&arr[i + 1]));
585                    i += 1;
586                }
587                "PROJECTION" => {
588                    let WKTValue::Array(arr) = &arr[i + 1] else {
589                        continue;
590                    };
591                    let proj = arr.first().map(|s| s.to_string()).unwrap_or_default();
592                    res.set_projection(proj);
593                    i += 1;
594                }
595                "ORDER" => {
596                    let WKTValue::Array(arr) = &arr[i + 1] else {
597                        continue;
598                    };
599                    let order = arr.first().map(|s| s.to_float() as usize).unwrap_or_default();
600                    res.set_order(order);
601                    i += 1;
602                }
603                // TODO: MODEL -> DYNAMIC[FRAMEEPOCH[2010.0],MODEL["NAD83(CSRS)v6 velocity grid"]] -> Stored as DeformationModel
604                _ => {}
605            }
606        }
607        i += 1;
608    }
609}
610
611// We removed _ and capitols in names, so if the input is "false_easting" -> "false easting"
612// Annex F of https://docs.ogc.org/is/18-010r7/18-010r7.html#221 has all names, codes and aliases.
613
614//? Used by EPSG project v12 [COMPLETE]:
615// ID: 214278
616// EPSG: 214276
617// LENGTHUNIT: 40304
618// ELLIPSOID: 12351
619// PARAMETER: 40825
620// AXIS: 28074
621// ANGLEUNIT: 23917
622// MEMBER: 19846
623// CS: 14093
624// DATUM: 10182
625// METHOD: 8986
626// SCALEUNIT: 5952
627// CONVERSION: 5857
628// BASEGEOGCRS: 5834
629// PROJCRS: 5830
630// GEOGCRS: 5768
631// OPERATIONACCURACY: 2953
632// ENSEMBLE: 2191
633// ENSEMBLEACCURACY: 2191
634// VDATUM: 1691
635// PARAMETERFILE: 1427 <----- PARAMETERFILE["Latitude difference file","alaska.las"],
636// ANCHOREPOCH: 1155
637// FRAMEEPOCH: 1024
638// GEODCRS: 749
639// TIMEUNIT: 217
640// PRIMEM: 150 (PRIMEM | PRIMEMERIDIAN)
641// MERIDIAN: 102
642// EDATUM: 29
643// BASEPROJCRS: 4
644
645//? Used by EPSG project v12 [TODO]:
646
647// NON OPERATIONS 1::
648// GEOIDMODEL: 1820
649// VERTCRS: 1713
650// DYNAMIC: 1024 <------ DYNAMIC[FRAMEEPOCH[2010.0],MODEL["NAD83(CSRS)v6 velocity grid"]]
651// COMPOUNDCRS: 703
652
653// NON OPERATIONS 2::
654// DEFININGTRANSFORMATION: 313
655// DERIVINGCONVERSION: 176
656// BASEVERTCRS: 172
657// ENGCRS: 29
658// DERIVEDPROJECTED: 4
659
660// OPERATIONS:
661// SOURCECRS: 3145
662// TARGETCRS: 3140
663// COORDINATEOPERATION: 2948 <------ THIS DOESNT EXIST IN STANDARD?
664// VERSION: 2948 <------ THIS DOESNT EXIST IN STANDARD?
665// STEP: 396
666// CONCATENATEDOPERATION: 192
667// POINTMOTIONOPERATION: 5
668
669// Ex.0:
670// PROJCRS["WGS 84 / Pseudo-Mercator",
671//    BASEGEOGCRS["WGS 84",
672//        ENSEMBLE["World Geodetic System 1984 ensemble",
673//            MEMBER["World Geodetic System 1984 (Transit)", ID["EPSG",1166]],
674//            MEMBER["World Geodetic System 1984 (G730)", ID["EPSG",1152]],
675//            MEMBER["World Geodetic System 1984 (G873)", ID["EPSG",1153]],
676//            MEMBER["World Geodetic System 1984 (G1150)", ID["EPSG",1154]],
677//            MEMBER["World Geodetic System 1984 (G1674)", ID["EPSG",1155]],
678//            MEMBER["World Geodetic System 1984 (G1762)", ID["EPSG",1156]],
679//            MEMBER["World Geodetic System 1984 (G2139)", ID["EPSG",1309]],
680//            MEMBER["World Geodetic System 1984 (G2296)", ID["EPSG",1383]],
681//            ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1,ID["EPSG",9001]],ID["EPSG",7030]],
682//            ENSEMBLEACCURACY[2],
683//            ID["EPSG",6326]],
684//       ID["EPSG",4326]],
685//    CONVERSION["Popular Visualisation Pseudo-Mercator",
686//        METHOD["Popular Visualisation Pseudo Mercator",ID["EPSG",1024]],
687//        PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9102]],ID["EPSG",8801]],
688//        PARAMETER["Longitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9102]],ID["EPSG",8802]],
689//        PARAMETER["False easting",0,LENGTHUNIT["metre",1,ID["EPSG",9001]],ID["EPSG",8806]],
690//        PARAMETER["False northing",0,LENGTHUNIT["metre",1,ID["EPSG",9001]],ID["EPSG",8807]],
691//        ID["EPSG",3856]],
692//    CS[Cartesian,2,ID["EPSG",4499]],
693//    AXIS["Easting (X)",east],
694//    AXIS["Northing (Y)",north],
695//    LENGTHUNIT["metre",1,ID["EPSG",9001]],
696//    ID["EPSG",3857]]
697
698// EX:
699// PROJCS["WGS 84 / Pseudo-Mercator",
700//      GEOGCS["WGS 84",
701//          DATUM["WGS_1984",
702//              SPHEROID["WGS 84",6378137,298.257223563,
703//                  AUTHORITY["EPSG","7030"]],
704//              AUTHORITY["EPSG","6326"]],
705//          PRIMEM["Greenwich",0,
706//              AUTHORITY["EPSG","8901"]],
707//          UNIT["degree",0.0174532925199433,
708//              AUTHORITY["EPSG","9122"]],
709//          AUTHORITY["EPSG","4326"]],
710//      PROJECTION["Mercator_1SP"],
711//      PARAMETER["central_meridian",0],
712//      PARAMETER["scale_factor",1],
713//      PARAMETER["false_easting",0],
714//      PARAMETER["false_northing",0],
715//      UNIT["metre",1,
716//          AUTHORITY["EPSG","9001"]],
717//      AXIS["Easting",EAST],
718//      AXIS["Northing",NORTH],
719//      EXTENSION["PROJ4","+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs"],
720//      AUTHORITY["EPSG","3857"]]
721
722// EX 2:
723// PROJCRS[
724//  "Xian 1980 / 3-degree Gauss-Kruger zone 30",
725//  BASEGEOGCRS[
726//      "Xian 1980",
727//      DATUM[
728//          "Xian 1980",
729//          ELLIPSOID[
730//              "IAG 1975",6378140,298.257,LENGTHUNIT["metre",1,ID["EPSG",9001]],
731//              ID["EPSG",7049]],
732//          ID["EPSG",6610]],
733//      ID["EPSG",4610]],
734//  CONVERSION["3-degree Gauss-Kruger zone 30",
735//      METHOD["Transverse Mercator",ID["EPSG",9807]],
736//      PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9102]],
737//          ID["EPSG",8801]],
738//      PARAMETER["Longitude of natural origin",90,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9102]],ID["EPSG",8802]],
739//      PARAMETER["Scale factor at natural origin",1,SCALEUNIT["unity",1,ID["EPSG",9201]],ID["EPSG",8805]],
740//      PARAMETER["False easting",30500000,LENGTHUNIT["metre",1,ID["EPSG",9001]],ID["EPSG",8806]],
741//      PARAMETER["False northing",0,LENGTHUNIT["metre",1,ID["EPSG",9001]],ID["EPSG",8807]],ID["EPSG",16290]],
742//      CS[Cartesian,2,ID["EPSG",4530]],
743//      AXIS["Northing (X)",north],
744//      AXIS["Easting (Y)",east],
745//      LENGTHUNIT["metre",1,ID["EPSG",9001]],
746//      ID["EPSG",2354]]
747
748// ------------------------------------------------------------------------------------------
749
750// COMMON:
751// ✅ PARAMETER
752// ✅ PROJECTION
753// ✅ DATUM
754// ✅ - VDATUM  |  VRF  |  VERTICALDATUM
755// ✅ - TDATUM | TIMEDATUM
756// ✅ - EDATUM | ENGINEERINGDATUM
757// ✅ - PDATUM | PARAMETRICDATUM
758// ✅ - DATUM | GEODETICDATUM | TRF
759// ✅ PRIMEM | PRIMEMERIDIAN | MERIDIAN
760// ✅ UNIT
761// ✅ AXIS (and AxisDirection)
762// - BEARING (used by axis rarely)
763
764// Coordinate operations:
765// - Transformation
766// - Conversion
767// - ConcatenatedOperation
768
769// Common CRS:
770// - CRS & CS
771// - - BoundCRS
772// - - CompoundCRS | COMPD_CS
773// - - EngineeringCRS | ENGCRS | LOCAL_CS
774// - - ParametricCRS
775// ✅ - PROJCRS | PROJECTEDCRS | PROJCS
776// - - TemporalCRS | TIMECRS
777// - - VERTCRS | VERTICALCRS | VERT_CS
778// ✅ - GEOGCS | GEOGCRS | GEOGRAPHICCRS | GEODCRS | GEODETICCRS | BASEGEODCRS | BASEGEOGCRS
779
780// used by transformation:
781// - SOURCECRS
782// - TARGETCRS
783
784// ------------------------------------------------------------------------------------
785
786// EXISTS BUT NOT USEFUL:
787// - EXTENSION
788
789// EXIST, esoteric:
790// - ORDER
791// - ANCHOR
792// - ANCHOREPOCH
793// - CONVERSION
794// - METHOD
795// - GEOIDMODEL
796// - PARAMETERFILE
797// - COORDINATEOPERATION
798// - INTERPOLATIONCRS
799// - OPERATIONACCURACY
800// - CONCATENATEDOPERATION
801// - STEP
802// - ABRIDGEDTRANSFORMATION
803// - DERIVINGCONVERSION
804// - CALENDAR
805// - TIMEORIGIN
806// - DYNAMIC
807// - FRAMEEPOCH
808// - MODEL
809// - VELOCITYGRID
810// - ENSEMBLE
811// - MEMBER
812// - ENSEMBLEACCURACY
813// - DERIVEDPROJCRS
814// - BASEPROJCRS
815// - PARAMETRICCRS
816// - BASEVERTCRS
817// - BASEENGCRS
818// - BASEPARAMCRS
819// - BASETIMECRS
820// - TRF
821// - VRF
822// - TEMPORALQUANTITY
823// - ENGINEERINGCRS
824// - EPOCH
825// - COORDEPOCH
826// - COORDINATEMETADATA
827// - POINTMOTIONOPERATION
828// - VERSION
829// - AXISMINVALUE
830// - AXISMAXVALUE
831// - RANGEMEANING
832// - exact
833// - wraparound
834
835// CS types - esoteric?:
836// - AFFINE
837// - CARTESIAN
838// - CYLINDRICAL
839// - ELLIPSOIDAL
840// - LINEAR
841// - PARAMETRIC
842// - POLAR
843// - SPHERICAL
844// - VERTICAL
845// - TEMPORAL
846// - TEMPORALCOUNT
847// - TEMPORALMEASURE
848// - ORDINAL
849// - TEMPORALDATETIME