Coverage for csvforwkt/crs.py: 94%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2"""One body can be defined by one of the two following shapes:
3 * a biaxial body
4 * a triaxial body
7At each shape, we can define three reference frames :
8 * planetocentric frame with a sphere for interoperability purpose
9 * planetocentric frame
10 * planetographic frame
12For a spherical shape, the planetocentric latitude and the planetographic
13latitude are identical. So a planetographic latitude is used.
15Planetographic longitude is usually defined such that the sub-observer
16longitude increases with time as seen by a distant, fixed observer
18Positive logitudes in one direction are defined with the following rule
20.. uml::
21 :caption: Positive longitude rules
23 start
25 if (historical reason or IAU_code >= 9000?) then (yes)
26 :East;
27 stop
28 else (no )
29 if (ocentric frame ?) then (yes)
30 :East;
31 stop
32 else (no)
33 if (rotation == "Direct" ?) then (yes)
34 :West;
35 stop
36 else (no)
37 :East;
38 stop
39 endif
40 endif
41 endif
43"""
44# pylint: disable=too-many-lines
45from abc import ABCMeta
46from abc import abstractmethod
47from abc import abstractproperty
48from enum import Enum
49from string import Template
50from typing import cast
51from typing import Dict
52from typing import Generator
53from typing import List
54from typing import Tuple
56import numpy as np # pylint: disable=import-error
57import pandas as pd # pylint: disable=import-error
59from .body import IAU_REPORT
60from .body import IBody
61from .body import ReferenceShape
62from .datum import Anchor
63from .datum import Datum
66class ICrs(metaclass=ABCMeta):
67 """High level class that handles a Coordinate Reference System."""
69 @classmethod
70 def __subclasshook__(cls, subclass):
71 return (
72 hasattr(subclass, "iau_code")
73 and callable(subclass.iau_code)
74 and hasattr(subclass, "wkt")
75 and callable(subclass.wkt)
76 and hasattr(subclass, "datum")
77 and callable(subclass.datum)
78 or NotImplemented
79 )
81 @abstractproperty # pylint: disable=no-self-use,bad-option-value,deprecated-decorator
82 def iau_code(self) -> int:
83 """IAU code.
85 :getter: Returns the IAU code
86 :type: int
87 """
88 raise NotImplementedError("Not implemented")
90 @abstractproperty # pylint: disable=no-self-use,bad-option-value,deprecated-decorator
91 def datum(self) -> Datum:
92 """Datum.
94 :getter: Returns the datum
95 :type: Datum
96 """
97 raise NotImplementedError("Not implemented")
99 @abstractmethod
100 def wkt(self) -> str:
101 """Returns the WKT description.
103 :getter: Returns the WKT description
104 :type: str
105 """
106 raise NotImplementedError("Not implemented")
109class CrsType(Enum):
110 """Type of CRS."""
112 OCENTRIC = "Ocentric"
113 OGRAPHIC = "Ographic"
116class BodyCrsCode(Enum):
117 """Code related to the shape and the coordinate reference system."""
119 SPHERE_OCENTRIC = ("Sphere", CrsType.OCENTRIC.value, 0)
120 ELLIPSE_OGRAPHIC = ("Ellipse", CrsType.OGRAPHIC.value, 1)
121 ELLIPSE_OCENTRIC = ("Ellipse", CrsType.OCENTRIC.value, 2)
122 TRIAXIAL_OGRAPHIC = ("Triaxial", CrsType.OGRAPHIC.value, 3)
123 TRIAXIAL_OCENTRIC = ("Triaxial", CrsType.OCENTRIC.value, 4)
125 def __init__(self, shape: str, reference: str, code: int):
126 """Creates the enum
128 Args:
129 shape (str): shape
130 reference (str): referential
131 code (int): code of (shape, referential)
132 """
133 self.shape: str = shape
134 self.reference: str = reference
135 self.code = code
137 def get_code(self, naif_code: int) -> int:
138 """Compute the code of the body based on the Naif code.
140 Args:
141 naif_code (int): naif code
143 Returns:
144 int: the code of the body
145 """
146 return naif_code * 100 + self.code
149@ICrs.register
150class BodyCrs(ICrs):
151 """The description of the body Coordinate Reference System."""
153 TEMPLATE_OGRAPHIC = """GEOGCRS["$name ($version) / Ographic",
154 $datum,
155\tCS[ellipsoidal, 2],
156\t AXIS["geodetic latitude (Lat)", north,
157\t ORDER[1],
158\t ANGLEUNIT["degree", 0.0174532925199433]],
159\t AXIS["geodetic longitude (Lon)", $direction,
160\t ORDER[2],
161\t ANGLEUNIT["degree", 0.0174532925199433]],
162\tID["IAU", $number, $version],
163\tREMARK["$remark"]]"""
165 TEMPLATE_OCENTRIC = """GEODCRS["$name ($version) / Ocentric",
166 $datum,
167\tCS[spherical, 2],
168\t AXIS["planetocentric latitude (U)", north,
169\t ORDER[1],
170\t ANGLEUNIT["degree", 0.0174532925199433]],
171\t AXIS["planetocentric longitude (V)", east,
172\t ORDER[2],
173\t ANGLEUNIT["degree", 0.0174532925199433]],
174\tID["IAU", $number, $version],
175\tREMARK["$remark"]]"""
177 TEMPLATE_SPHERE = """GEOGCRS["$name ($version) - Sphere / Ocentric",
178 $datum,
179\tCS[ellipsoidal, 2],
180\t AXIS["geodetic latitude (Lat)", north,
181\t ORDER[1],
182\t ANGLEUNIT["degree", 0.0174532925199433]],
183\t AXIS["geodetic longitude (Lon)", east,
184\t ORDER[2],
185\t ANGLEUNIT["degree", 0.0174532925199433]],
186\tID["IAU", $number, $version],
187\tREMARK["$remark"]]"""
189 def __init__(
190 self, datum: Datum, number_body: int, direction: str, crs_type: CrsType
191 ):
192 """Create a Coordinate Reference System for a celestial body
194 Args:
195 datum (Datum): datum of the body
196 number_body (int): IAU code
197 direction (str): rotation sens of the body
198 crs_type (CrsType): type of CRS
199 """
200 self.__datum: Datum = datum
201 self.__crs_type: CrsType = crs_type
202 self.__direction: str = self._create_direction(
203 datum.name, direction, crs_type, number_body
204 )
205 self.__name: str = datum.name
207 template, number = self._get_template_and_number(number_body)
208 self.__number: int = number
209 self.__template: str = template
211 def _get_template_and_number( # pylint: disable=no-self-use
212 self, naif_code: int
213 ) -> Tuple[str, int]:
214 """Returns the template and the number
216 Args:
217 naif_code (int): naif code
219 Raises:
220 ValueError: "Unknown shape or CRS
222 Returns:
223 Tuple[str, int]: template and number
224 """
225 template: str
226 number: int
228 if self.crs_type == CrsType.OCENTRIC:
229 if self.datum.body.shape == ReferenceShape.SPHERE:
230 template = BodyCrs.TEMPLATE_SPHERE
231 number = BodyCrsCode.SPHERE_OCENTRIC.get_code(naif_code)
232 elif self.datum.body.shape == ReferenceShape.ELLIPSE:
233 template = BodyCrs.TEMPLATE_OCENTRIC
234 number = BodyCrsCode.ELLIPSE_OCENTRIC.get_code(naif_code)
235 elif self.datum.body.shape == ReferenceShape.TRIAXIAL:
236 template = BodyCrs.TEMPLATE_OCENTRIC
237 number = BodyCrsCode.TRIAXIAL_OCENTRIC.get_code(naif_code)
238 else:
239 raise ValueError(
240 f"Unknown shape : {self.datum.body.shape} for {self.crs_type}"
241 )
242 elif self.crs_type == CrsType.OGRAPHIC:
243 template = BodyCrs.TEMPLATE_OGRAPHIC
244 if self.datum.body.shape == ReferenceShape.ELLIPSE:
245 number = BodyCrsCode.ELLIPSE_OGRAPHIC.get_code(naif_code)
246 elif self.datum.body.shape == ReferenceShape.TRIAXIAL:
247 number = BodyCrsCode.TRIAXIAL_OGRAPHIC.get_code(naif_code)
248 else:
249 raise ValueError(
250 f"Unknown shape : {self.datum.body.shape} for {self.crs_type}"
251 )
252 else:
253 raise ValueError(f"Unknown CRS : {self.crs_type}")
255 return template, number
257 def _create_direction(
258 self,
259 name: str,
260 rotation: str,
261 crs_type: CrsType,
262 number_body: int,
263 ): # pylint: disable=no-self-use
264 """Returns the direction sens according to the rotation.
266 Args:
267 name (str): body name
268 rotation (str): rotation of the body
269 crs_type (CrsType): Type of CRS
270 number_body (int): IAU code of the body
273 Returns:
274 str: _description_
275 """
276 direction: str
277 # longitude ographic is always to East for small bodies, comets, dwarf planets
278 # historical reason for SUN, EARTH and MOON
279 if number_body >= 90000 or name.upper() in ["SUN", "EARTH", "MOON"]:
280 direction = "east"
282 # always to east in ocentric
283 elif crs_type == CrsType.OCENTRIC:
284 direction = "east"
286 # when Direct => West
287 elif rotation == "Direct":
288 direction = "west"
290 # last case : east
291 else:
292 direction = "east"
293 return direction
295 def _create_remark(self) -> str:
296 """Returns the content of the remark.
298 Returns:
299 str: the content of the remark
300 """
301 result: str
302 if self.datum.body.warning is None:
303 result = IAU_REPORT.SOURCE_IAU
304 else:
305 result = self.datum.body.warning + IAU_REPORT.SOURCE_IAU
306 return result
308 @property
309 def crs_type(self) -> CrsType:
310 """Returns the type of CRS.
312 Returns:
313 CrsType: Type of CRS
314 """
315 return self.__crs_type
317 @property
318 def name(self) -> str:
319 """Returns the name of the body.
321 Returns:
322 str: the name of the body
323 """
324 return self.__name
326 @property
327 def datum(self) -> Datum:
328 """Returns the datum of the body.
330 Returns:
331 Datum: the datum of the body
332 """
333 return self.__datum
335 @property
336 def direction(self) -> str:
337 """Returns the direction where the longitude is counted positively.
339 Returns:
340 str: the direction
341 """
342 return self.__direction
344 @property
345 def iau_code(self) -> int:
346 """Returns the IAU code.
348 Returns:
349 str: the IAU code
350 """
351 return self.__number
353 def wkt(self) -> str:
354 """Returns the WKT of the celestial body.
356 Returns:
357 str: the WKT of the celestial body
358 """
359 biaxialbody_template = Template(self.__template)
360 datum = biaxialbody_template.substitute(
361 name=self.name,
362 version=IAU_REPORT.VERSION,
363 datum=self.datum.wkt(),
364 number=self.iau_code,
365 direction=self.direction,
366 remark=self._create_remark(),
367 )
368 return datum
371class Planetocentric:
372 """Computes the planetocentric coordinate reference system."""
374 def __init__(self, row: pd.Series, ref_shape: ReferenceShape):
375 """Creates a description of a planetocentric Coordinate Reference
376 System.
378 Args:
379 row (pd.Series): description of the current body
380 ref_shape(ReferenceShape) : Reference of the shape
382 Returns:
383 ICrs: Coordinate Reference System description
384 """
385 self.__row: pd.dataframe = row
386 self.__ref_shape: ReferenceShape = ref_shape
387 self.__crs: BodyCrs = self._crs()
389 @property
390 def row(self) -> pd.DataFrame:
391 """Description of the current body.
393 Returns:
394 str: Description of the current body
395 """
396 return self.__row
398 @property
399 def ref_shape(self) -> ReferenceShape:
400 """Shape related to this coordinate reference system.
402 Returns:
403 ReferenceShape: type of shape
404 """
405 return self.__ref_shape
407 @property
408 def crs_type(self) -> CrsType:
409 """Type of coordinate reference system.
411 Returns:
412 CrsType: type of coordinate reference system
413 """
414 return CrsType.OCENTRIC
416 @property
417 def crs(self) -> BodyCrs:
418 """Coordinate reference system of the body.
420 Returns:
421 BodyCrs: coordinate reference system of the body
422 """
423 return self.__crs
425 def _create_body(self) -> IBody:
426 """Creates the description of the coordinate reference system for the
427 body.
429 Returns:
430 IBody: the coordinate reference system for the body
431 """
432 return IBody.create(
433 self.ref_shape,
434 self.row["Body"],
435 self.row["IAU2015_Semimajor"],
436 self.row["IAU2015_Semiminor"],
437 self.row["IAU2015_Axisb"],
438 self.row["IAU2015_Mean"],
439 )
441 def _create_datum(self, body: IBody) -> Datum:
442 """Creates the description of the datum related to the body.
444 Args:
445 body (IBody): the body
447 Returns:
448 Datum: the datum related to the body
449 """
450 anchor: Anchor = Anchor(
451 f"{self.row['origin_long_name']} : {self.row['origin_lon_pos']}"
452 )
453 return Datum.create(self.row["Body"], body, anchor)
455 def _create_crs(self, datum: Datum) -> BodyCrs:
456 """Creates a description of the planetocentric reference system based
457 on the datum.
459 Args:
460 datum (Datum): datum of the body
462 Returns:
463 BodyCrs: the planetocentric reference system based on the datum
464 """
465 return BodyCrs(
466 datum,
467 self.row["Naif_id"],
468 self.row["rotation"],
469 CrsType.OCENTRIC,
470 )
472 def _crs(self) -> BodyCrs:
473 """Creates a description of a planetocentric Coordinate Reference
474 System.
476 Returns:
477 BodyCrs: Coordinate Reference System description of the body
478 """
479 shape: IBody = self._create_body()
480 datum: Datum = self._create_datum(shape)
481 crs: BodyCrs = self._create_crs(datum)
482 return crs
484 def wkt(self) -> str:
485 """Returns the WKT of the coordinate reference system.
487 Returns:
488 str: the WKT of the coordinate reference system
489 """
490 return self.__crs.wkt()
493class Planetographic(Planetocentric):
494 """Computes the planetographic coordinate reference system."""
496 @property
497 def crs_type(self) -> CrsType:
498 """Type of coordinate reference system.
500 Returns:
501 CrsType: type of coordinate reference system
502 """
503 return CrsType.OGRAPHIC
505 def _create_crs(self, datum: Datum) -> BodyCrs:
506 """Creates a planetographic coordinate reference system based on a
507 datum.
509 Args:
510 datum (Datum): datum of the body
512 Returns:
513 BodyCrs: planetographic coordinate reference system
514 """
515 return BodyCrs(
516 datum,
517 self.row["Naif_id"],
518 self.row["rotation"],
519 CrsType.OGRAPHIC,
520 )
523class Conversion:
524 """Projection elements."""
526 TEMPLATE_PARAMETER = """PARAMETER["$parameter_name", $parameter_value,
527 $unit,
528 ID["$authority", $authority_code]]"""
530 TEMPLATE_CONVERSION = """CONVERSION["$conversion_name",
531 METHOD["$method_name",
532 ID["$authority", $authority_code]],
533 $params],"""
535 def __init__(
536 self,
537 conversion_name: str,
538 method_name: str,
539 method_id: str,
540 projection: List[str],
541 ) -> None:
542 """Creates the projection elements.
544 Args:
545 conversion_name (str): name of the projection
546 method_name (str): name of the method
547 method_id (str): method ID
548 projection (List[str]): projection elements
549 """
550 self.__conversion_name = conversion_name
551 self.__method_name = method_name
552 self.__method_id = method_id
553 self.__projection = projection
555 @property
556 def conversion_name(self) -> str:
557 """Returns the conversion name.
559 Returns:
560 str: the conversion
561 """
562 return self.__conversion_name
564 @property
565 def method_name(self) -> str:
566 """Returns the method name.
568 Returns:
569 str: the method name
570 """
571 return self.__method_name
573 @property
574 def method_id(self) -> str:
575 """Returns the method ID.
577 Returns:
578 str: the method ID
579 """
580 return self.__method_id
582 @property
583 def projection(self) -> List[str]:
584 """Returns the projection elements.
586 Returns:
587 List[str]: the projection elements
588 """
589 return self.__projection
591 def wkt(self) -> str:
592 """Returns the WKT of the projection elements.
594 Returns:
595 str: the WKT
596 """
597 parameter_template = Template(Conversion.TEMPLATE_PARAMETER)
598 parameters = np.array(
599 [
600 param
601 for param in self.projection[3 : len(self.projection)]
602 if param is not None
603 ]
604 )
605 parameters = parameters.reshape(int(len(parameters) / 2), 2)
606 params: List[str] = list()
607 for parameter in parameters:
608 method_and_map: List[
609 str
610 ] = ProjectionBody.METHOD_AND_PARAM_MAPPING[parameter[0]]
611 param: str = parameter_template.substitute(
612 parameter_name=parameter[0],
613 parameter_value=parameter[1],
614 unit=method_and_map[2],
615 authority=method_and_map[0],
616 authority_code=method_and_map[1],
617 )
618 params.append(param)
619 conversion_template = Template(Conversion.TEMPLATE_CONVERSION)
621 conversion = conversion_template.substitute(
622 conversion_name=self.projection[1],
623 method_name=self.projection[2],
624 authority=ProjectionBody.METHOD_AND_PARAM_MAPPING[
625 self.projection[2]
626 ][0],
627 authority_code=ProjectionBody.METHOD_AND_PARAM_MAPPING[
628 self.projection[2]
629 ][1],
630 params=",\n\t\t".join(params),
631 )
632 return conversion
635@ICrs.register
636class ProjectionBody(ICrs):
637 """Coordinate Reference System of the projected body."""
639 PROJECTION_DATA = [
640 [
641 10,
642 "Equirectangular, clon = 0",
643 "Equidistant Cylindrical",
644 "Latitude of 1st standard parallel",
645 0,
646 "Longitude of natural origin",
647 0,
648 "False easting",
649 0,
650 "False northing",
651 0,
652 None,
653 None,
654 None,
655 None,
656 ],
657 [
658 15,
659 "Equirectangular, clon = 180",
660 "Equidistant Cylindrical",
661 "Latitude of 1st standard parallel",
662 0,
663 "Longitude of natural origin",
664 180,
665 "False easting",
666 0,
667 "False northing",
668 0,
669 None,
670 None,
671 None,
672 None,
673 ],
674 [
675 20,
676 "Sinusoidal, clon = 0",
677 "Sinusoidal",
678 "Longitude of natural origin",
679 0,
680 "False easting",
681 0,
682 "False northing",
683 0,
684 None,
685 None,
686 None,
687 None,
688 None,
689 None,
690 ],
691 [
692 25,
693 "Sinusoidal, clon = 180",
694 "Sinusoidal",
695 "Longitude of natural origin",
696 180,
697 "False easting",
698 0,
699 "False northing",
700 0,
701 None,
702 None,
703 None,
704 None,
705 None,
706 None,
707 ],
708 [
709 30,
710 "North Polar",
711 "Polar Stereographic (variant A)",
712 "Latitude of natural origin",
713 90,
714 "Longitude of natural origin",
715 0,
716 "Scale factor at natural origin",
717 1,
718 "False easting",
719 0,
720 "False northing",
721 0,
722 None,
723 None,
724 ],
725 [
726 35,
727 "South Polar",
728 "Polar Stereographic (variant A)",
729 "Latitude of natural origin",
730 -90,
731 "Longitude of natural origin",
732 0,
733 "Scale factor at natural origin",
734 1,
735 "False easting",
736 0,
737 "False northing",
738 0,
739 None,
740 None,
741 ],
742 [
743 40,
744 "Mollweide, clon = 0",
745 "Mollweide",
746 "Longitude of natural origin",
747 0,
748 "False easting",
749 0,
750 "False northing",
751 0,
752 None,
753 None,
754 None,
755 None,
756 None,
757 None,
758 ],
759 [
760 45,
761 "Mollweide, clon = 180",
762 "Mollweide",
763 "Longitude of natural origin",
764 180,
765 "False easting",
766 0,
767 "False northing",
768 0,
769 None,
770 None,
771 None,
772 None,
773 None,
774 None,
775 ],
776 [
777 50,
778 "Robinson, clon = 0",
779 "Robinson",
780 "Longitude of natural origin",
781 0,
782 "False easting",
783 0,
784 "False northing",
785 0,
786 None,
787 None,
788 None,
789 None,
790 None,
791 None,
792 ],
793 [
794 55,
795 "Robinson, clon = 180",
796 "Robinson",
797 "Longitude of natural origin",
798 180,
799 "False easting",
800 0,
801 "False northing",
802 0,
803 None,
804 None,
805 None,
806 None,
807 None,
808 None,
809 ],
810 [
811 60,
812 "Tranverse Mercator",
813 "Transverse Mercator",
814 "Latitude of natural origin",
815 0,
816 "Longitude of natural origin",
817 0,
818 "Scale factor at natural origin",
819 1,
820 "False easting",
821 0,
822 "False northing",
823 0,
824 None,
825 None,
826 ],
827 [
828 65,
829 "Orthographic, clon = 0",
830 "Orthographic",
831 "Latitude of natural origin",
832 0,
833 "Longitude of natural origin",
834 0,
835 "False easting",
836 0,
837 "False northing",
838 0,
839 None,
840 None,
841 None,
842 None,
843 ],
844 [
845 70,
846 "Orthographic, clon = 180",
847 "Orthographic",
848 "Latitude of natural origin",
849 0,
850 "Longitude of natural origin",
851 180,
852 "False easting",
853 0,
854 "False northing",
855 0,
856 None,
857 None,
858 None,
859 None,
860 ],
861 [
862 75,
863 "Lambert Conic Conformal",
864 "Lambert Conic Conformal (2SP)",
865 "Latitude of false origin",
866 40,
867 "Longitude of false origin",
868 0,
869 "Latitude of 1st standard parallel",
870 20,
871 "Latitude of 2nd standard parallel",
872 60,
873 "Easting at false origin",
874 0,
875 "Northing at false origin",
876 0,
877 ],
878 [
879 80,
880 "Lambert Azimuthal Equal Area",
881 "Lambert Azimuthal Equal Area",
882 "Latitude of natural origin",
883 40,
884 "Longitude of natural origin",
885 0,
886 "False easting",
887 0,
888 "False northing",
889 0,
890 None,
891 None,
892 None,
893 None,
894 ],
895 [
896 85,
897 "Albers Equal Area",
898 "Albers Equal Area",
899 "Latitude of false origin",
900 40,
901 "Longitude of false origin",
902 0,
903 "Latitude of 1st standard parallel",
904 20,
905 "Latitude of 2nd standard parallel",
906 60,
907 "Easting at false origin",
908 0,
909 "Northing at false origin",
910 0,
911 ],
912 ]
914 METHOD_AND_PARAM_MAPPING: Dict[str, List] = {
915 "Lambert Azimuthal Equal Area (Spherical)": ["EPSG", 1027],
916 "Equidistant Cylindrical": ["EPSG", 1028],
917 "Equidistant Cylindrical (Spherical)": ["EPSG", 1029],
918 "Scale factor at natural origin": [
919 "EPSG",
920 8805,
921 'SCALEUNIT["unity",1,ID["EPSG", 9201]]',
922 ],
923 "False easting": [
924 "EPSG",
925 8806,
926 'LENGTHUNIT["metre",1,ID["EPSG", 9001]]',
927 ],
928 "False northing": [
929 "EPSG",
930 8807,
931 'LENGTHUNIT["metre",1,ID["EPSG", 9001]]',
932 ],
933 "Latitude of natural origin": [
934 "EPSG",
935 8801,
936 'ANGLEUNIT["degree",0.0174532925199433,ID["EPSG", 9122]]',
937 ],
938 "Longitude of natural origin": [
939 "EPSG",
940 8802,
941 'ANGLEUNIT["degree",0.0174532925199433,ID["EPSG", 9122]]',
942 ],
943 "Latitude of false origin": [
944 "EPSG",
945 8821,
946 'ANGLEUNIT["degree",0.0174532925199433,ID["EPSG", 9122]]',
947 ],
948 "Longitude of false origin": [
949 "EPSG",
950 8822,
951 'ANGLEUNIT["degree",0.0174532925199433,ID["EPSG", 9122]]',
952 ],
953 "Latitude of 1st standard parallel": [
954 "EPSG",
955 8823,
956 'ANGLEUNIT["degree",0.0174532925199433,ID["EPSG", 9122]]',
957 ],
958 "Latitude of 2nd standard parallel": [
959 "EPSG",
960 8824,
961 'ANGLEUNIT["degree",0.0174532925199433,ID["EPSG", 9122]]',
962 ],
963 "Easting at false origin": [
964 "EPSG",
965 8826,
966 'LENGTHUNIT["metre",1,ID["EPSG", 9001]]',
967 ],
968 "Northing at false origin": [
969 "EPSG",
970 8827,
971 'LENGTHUNIT["metre",1,ID["EPSG", 9001]]',
972 ],
973 "Sinusoidal": ["PROJ", '"SINUSOIDAL"'],
974 "Robinson": ["PROJ", '"ROBINSON"'],
975 "Mollweide": ["PROJ", '"MOLLWEIDE"'],
976 "Transverse Mercator": ["EPSG", 9807],
977 "Lambert Conic Conformal (2SP)": ["EPSG", 9802],
978 "Polar Stereographic (variant A)": ["EPSG", 9810],
979 "Lambert Azimuthal Equal Area": ["EPSG", 9820],
980 "Albers Equal Area": ["EPSG", 9822],
981 "Orthographic": ["EPSG", 9840],
982 }
984 TEMPLATE_OCENTRIC_SPHERE = """PROJCRS["$projection_name",
985 BASEGEOGCRS["$name ($version) $reference",
986 $datum,
987 ID["IAU", $number_body, $version]],
988 $conversion
989 CS[Cartesian, 2],
990 AXIS["$direction_name", $direction,
991 ORDER[1],
992 LENGTHUNIT["metre", 1]],
993 AXIS["Northing (N)", north,
994 ORDER[2],
995 LENGTHUNIT["metre", 1]],
996 ID["IAU", $number, $version]]"""
998 TEMPLATE_OCENTRIC = """PROJCRS["$projection_name",
999 BASEGEODCRS["$name ($version) $reference",
1000 $datum,
1001 ID["IAU", $number_body, $version]],
1002 $conversion
1003 CS[Cartesian, 2],
1004 AXIS["$direction_name", $direction,
1005 ORDER[1],
1006 LENGTHUNIT["metre", 1]],
1007 AXIS["Northing (N)", north,
1008 ORDER[2],
1009 LENGTHUNIT["metre", 1]],
1010 ID["IAU", $number, $version]]"""
1012 TEMPLATE_OGRAPHIC = """PROJCRS["$projection_name",
1013 BASEGEOGCRS["$name ($version) $reference",
1014 $datum,
1015 ID["IAU", $number_body, $version]],
1016 $conversion
1017 CS[Cartesian, 2],
1018 AXIS["$direction_name", $direction,
1019 ORDER[1],
1020 LENGTHUNIT["metre", 1]],
1021 AXIS["Northing (N)", north,
1022 ORDER[2],
1023 LENGTHUNIT["metre", 1]],
1024 ID["IAU", $number, $version]]"""
1026 def __init__(
1027 self, body_crs: BodyCrs, projection: List[str], template: str
1028 ) -> None:
1029 """Creates the projected body.
1031 Args:
1032 body_crs (BodyCrs): Coordinate Reference System of the body
1033 projection (List[str]): projection elements
1034 """
1035 self.__body_crs: BodyCrs = body_crs
1036 self.__projection: List[str] = projection
1037 self.__template: str = template
1038 self.__conversion: Conversion = self._create_conversion(
1039 projection[1], projection[2], "METHOD", projection
1040 )
1042 def _create_conversion( # pylint: disable=no-self-use
1043 self,
1044 conversion_name: str,
1045 method_name: str,
1046 method_id: str,
1047 projection: List[str],
1048 ) -> Conversion:
1049 """Create the conversion.
1051 Args:
1052 conversion_name (str): conversion name
1053 method_name (str): method name
1054 method_id (str): method ID
1055 projection (List[str]): projection elements
1057 Returns:
1058 Conversion: Coversion
1059 """
1060 return Conversion(conversion_name, method_name, method_id, projection)
1062 def _create_projection(self) -> str:
1063 """Returns the projection name.
1065 Returns:
1066 str: the projection name
1067 """
1068 projection: str = (
1069 f"{self.body_crs.datum.body.name} ({IAU_REPORT.VERSION}) "
1070 )
1071 if (
1072 self.body_crs.crs_type == CrsType.OCENTRIC
1073 and self.body_crs.datum.body.shape == ReferenceShape.SPHERE
1074 ):
1075 reference = (
1076 projection
1077 + "- "
1078 + self.body_crs.datum.body.shape.value
1079 + " / "
1080 + self.body_crs.crs_type.value
1081 + f"/ {self.projection[1]}"
1082 )
1083 else:
1084 reference = (
1085 projection
1086 + "/ "
1087 + self.body_crs.crs_type.value
1088 + f"/ {self.projection[1]}"
1089 )
1090 return reference
1092 def _create_reference(self) -> str:
1093 """Returns the reference name.
1095 Returns:
1096 str: the reference name
1097 """
1098 reference: str
1099 if (
1100 self.body_crs.crs_type == CrsType.OCENTRIC
1101 and self.body_crs.datum.body.shape == ReferenceShape.SPHERE
1102 ):
1103 reference = (
1104 "- "
1105 + self.body_crs.datum.body.shape.value
1106 + " / "
1107 + self.body_crs.crs_type.value
1108 )
1109 else:
1110 reference = "/ " + self.body_crs.crs_type.value
1111 return reference
1113 @property
1114 def body_crs(self) -> BodyCrs:
1115 """Returns the body description.
1117 Returns:
1118 str: the body description
1119 """
1120 return self.__body_crs
1122 @property
1123 def projection(self) -> List[str]:
1124 """Returns the projection elements.
1126 Returns:
1127 str: the projection elements
1128 """
1129 return self.__projection
1131 @property
1132 def template(self) -> str:
1133 """Returns the template.
1135 Returns:
1136 str: the template
1137 """
1138 return self.__template
1140 @property
1141 def iau_code(self) -> int:
1142 """Returns the IAU code.
1144 Returns:
1145 int: the IAU code
1146 """
1147 return self.body_crs.iau_code + int(self.projection[0])
1149 @property
1150 def datum(self) -> Datum:
1151 """Datum.
1153 :getter: Returns the datum
1154 :type: Datum
1155 """
1156 return self.body_crs.datum
1158 @staticmethod
1159 def create(body_crs: BodyCrs, projection: List[str]) -> "ProjectionBody":
1160 """Create a projected coordinate reference system.
1162 Args:
1163 body_crs (BodyCrs): body CRS description
1164 projection (List[str]): projection elements
1166 Raises:
1167 ValueError: Unknown CRS type
1169 Returns:
1170 ProjectionBody: projected CRS description
1171 """
1172 result: ProjectionBody
1173 if (
1174 body_crs.crs_type == CrsType.OCENTRIC
1175 and body_crs.datum.body.shape == ReferenceShape.SPHERE
1176 ):
1177 result = ProjectionBody(
1178 body_crs, projection, ProjectionBody.TEMPLATE_OCENTRIC_SPHERE
1179 )
1180 elif body_crs.crs_type == CrsType.OCENTRIC:
1181 result = ProjectionBody(
1182 body_crs, projection, ProjectionBody.TEMPLATE_OCENTRIC
1183 )
1184 elif body_crs.crs_type == CrsType.OGRAPHIC:
1185 result = ProjectionBody(
1186 body_crs, projection, ProjectionBody.TEMPLATE_OGRAPHIC
1187 )
1188 else:
1189 raise ValueError(f"Unknown CRS type: {CrsType}")
1190 return result
1192 def wkt(self) -> str:
1193 """Returns the WKT of the projected body.
1195 Returns:
1196 str: the WKT of the projected body
1197 """
1198 proj_body_template = Template(self.template)
1199 return proj_body_template.substitute(
1200 projection_name=self._create_projection(),
1201 name=self.body_crs.name,
1202 version=IAU_REPORT.VERSION,
1203 datum=self.body_crs.datum.wkt(),
1204 number=self.body_crs.iau_code + int(self.projection[0]),
1205 number_body=self.body_crs.iau_code,
1206 conversion=self.__conversion.wkt(),
1207 reference=self._create_reference(),
1208 direction=self.body_crs.direction,
1209 direction_name="Westing (W)"
1210 if self.body_crs.direction == "west"
1211 else "Easting (E)",
1212 )
1214 @staticmethod
1215 def iter_projection(body_crs: BodyCrs) -> Generator:
1216 """Iter on the different projections of the projected body
1218 Args:
1219 body_crs (BodyCrs): Body to project
1221 Yields:
1222 Generator: Iterator of the projections of the body.
1223 """
1224 for projection in ProjectionBody.PROJECTION_DATA:
1225 yield ProjectionBody.create(body_crs, cast(List[str], projection))