compass-lib 0.0.2__py3-none-any.whl → 0.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- compass_lib/__init__.py +113 -3
- compass_lib/commands/__init__.py +2 -0
- compass_lib/commands/convert.py +223 -36
- compass_lib/commands/encrypt.py +33 -7
- compass_lib/commands/geojson.py +118 -0
- compass_lib/commands/main.py +1 -1
- compass_lib/constants.py +84 -36
- compass_lib/enums.py +292 -84
- compass_lib/errors.py +86 -0
- compass_lib/geo_utils.py +47 -0
- compass_lib/geojson.py +1024 -0
- compass_lib/interface.py +332 -0
- compass_lib/io.py +246 -0
- compass_lib/models.py +217 -95
- compass_lib/plot/__init__.py +28 -0
- compass_lib/plot/models.py +265 -0
- compass_lib/plot/parser.py +610 -0
- compass_lib/project/__init__.py +36 -0
- compass_lib/project/format.py +158 -0
- compass_lib/project/models.py +494 -0
- compass_lib/project/parser.py +638 -0
- compass_lib/survey/__init__.py +24 -0
- compass_lib/survey/format.py +284 -0
- compass_lib/survey/models.py +160 -0
- compass_lib/survey/parser.py +842 -0
- compass_lib/validation.py +74 -0
- {compass_lib-0.0.2.dist-info → compass_lib-0.0.3.dist-info}/METADATA +8 -11
- compass_lib-0.0.3.dist-info/RECORD +31 -0
- {compass_lib-0.0.2.dist-info → compass_lib-0.0.3.dist-info}/entry_points.txt +2 -1
- compass_lib/encoding.py +0 -27
- compass_lib/parser.py +0 -435
- compass_lib/utils.py +0 -15
- compass_lib-0.0.2.dist-info/RECORD +0 -16
- {compass_lib-0.0.2.dist-info → compass_lib-0.0.3.dist-info}/WHEEL +0 -0
- {compass_lib-0.0.2.dist-info → compass_lib-0.0.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Validation utilities for Compass data.
|
|
3
|
+
|
|
4
|
+
This module provides validation functions for station names and other
|
|
5
|
+
data elements based on Compass format specifications.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import calendar
|
|
9
|
+
import re
|
|
10
|
+
from re import Pattern
|
|
11
|
+
|
|
12
|
+
# Station name pattern: printable ASCII characters (0x21-0x7F), no spaces
|
|
13
|
+
# The original Compass spec limited to 12 chars, but modern files often exceed this
|
|
14
|
+
# We allow any length but still require valid printable ASCII
|
|
15
|
+
STATION_NAME_PATTERN: Pattern[str] = re.compile(r"^[\x21-\x7f]+$")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def is_valid_station_name(name: str) -> bool:
|
|
19
|
+
"""Check if a station name is valid.
|
|
20
|
+
|
|
21
|
+
Valid station names:
|
|
22
|
+
- 1 or more characters (no upper limit enforced)
|
|
23
|
+
- Printable ASCII only (0x21-0x7F)
|
|
24
|
+
- No spaces or control characters
|
|
25
|
+
|
|
26
|
+
Note: The original Compass format limited station names to 12 characters,
|
|
27
|
+
but many modern Compass files use longer names. This validation accepts
|
|
28
|
+
any length to ensure compatibility with real-world data.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
name: Station name to validate
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
True if valid, False otherwise
|
|
35
|
+
"""
|
|
36
|
+
if not name:
|
|
37
|
+
return False
|
|
38
|
+
return bool(STATION_NAME_PATTERN.match(name))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def validate_station_name(name: str) -> None:
|
|
42
|
+
"""Validate a station name, raising an error if invalid.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
name: Station name to validate
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
ValueError: If the station name is invalid
|
|
49
|
+
"""
|
|
50
|
+
if not is_valid_station_name(name):
|
|
51
|
+
# Escape non-printable characters for error message
|
|
52
|
+
escaped = ""
|
|
53
|
+
for char in name:
|
|
54
|
+
if ord(char) < 0x20 or ord(char) > 0x7F:
|
|
55
|
+
escaped += f"\\x{ord(char):02x}"
|
|
56
|
+
else:
|
|
57
|
+
escaped += char
|
|
58
|
+
msg = f"Invalid station name: {escaped}"
|
|
59
|
+
raise ValueError(msg)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def days_in_month(month: int, year: int) -> int:
|
|
63
|
+
"""Get the number of days in a month, accounting for leap years.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
month: Month (1-12)
|
|
67
|
+
year: Year
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Number of days in the month
|
|
71
|
+
"""
|
|
72
|
+
# monthrange returns (weekday_of_first_day, num_days_in_month)
|
|
73
|
+
_, num_days = calendar.monthrange(year, month)
|
|
74
|
+
return num_days
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: compass_lib
|
|
3
|
-
Version: 0.0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: Compass Parser Library.
|
|
5
5
|
Keywords: cave,survey,karst
|
|
6
6
|
Author-email: Jonathan Dekhtiar <jonathan@dekhtiar.com>
|
|
7
7
|
Maintainer-email: Jonathan Dekhtiar <jonathan@dekhtiar.com>
|
|
@@ -20,12 +20,17 @@ Classifier: Operating System :: OS Independent
|
|
|
20
20
|
Classifier: Programming Language :: Python :: 3
|
|
21
21
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
22
|
License-File: LICENSE
|
|
23
|
+
Requires-Dist: geojson>=3.2,<4
|
|
23
24
|
Requires-Dist: orjson>=3.10,<3.12
|
|
24
25
|
Requires-Dist: pydantic>=2.12,<2.13
|
|
26
|
+
Requires-Dist: pydantic-extra-types>=2.11,<3.0
|
|
27
|
+
Requires-Dist: pyIGRF14==1.0.4
|
|
28
|
+
Requires-Dist: pyproj>=3.7.1,<3.8
|
|
29
|
+
Requires-Dist: utm>=0.8.1,<0.9
|
|
25
30
|
Requires-Dist: cryptography>=44.0.0,<47.0.0 ; extra == "test"
|
|
26
31
|
Requires-Dist: python-dotenv>=1.0.0,<2.0.0 ; extra == "test"
|
|
27
32
|
Requires-Dist: deepdiff>=7.0,<9.0 ; extra == "test"
|
|
28
|
-
Requires-Dist: pytest>=8.0.0,<
|
|
33
|
+
Requires-Dist: pytest>=8.0.0,<10.0.0 ; extra == "test"
|
|
29
34
|
Requires-Dist: pytest-cov>=5.0.0,<8.0.0 ; extra == "test"
|
|
30
35
|
Requires-Dist: pytest-dotenv>=0.5.0,<1.0.0 ; extra == "test"
|
|
31
36
|
Requires-Dist: pytest-env>=1.1.3,<2.0.0 ; extra == "test"
|
|
@@ -39,14 +44,6 @@ Provides-Extra: test
|
|
|
39
44
|
|
|
40
45
|
# Compass Python Lib
|
|
41
46
|
|
|
42
|
-
Short instructions
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
python demo.py
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
--------------------
|
|
49
|
-
|
|
50
47
|
## Conversion commands:
|
|
51
48
|
|
|
52
49
|
```bash
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
compass_lib/__init__.py,sha256=GmS4B4rae0P8zy4-givpmeyFUSqKRJ8WEERgxro9Gxs,3594
|
|
2
|
+
compass_lib/constants.py,sha256=TApFKLFXywSS_iFx_UBBO9mQhjW2-1f8Wsu0J-Mr-rU,3144
|
|
3
|
+
compass_lib/enums.py,sha256=PgIOhqb6qUBHZhZpVaZfQ_qG6DCp2nfQD4SghM-lAoM,9667
|
|
4
|
+
compass_lib/errors.py,sha256=0D5jCIroPdRX2oVx1PK_lotYKDdjc_94jeFKQhW8pBE,2473
|
|
5
|
+
compass_lib/geo_utils.py,sha256=5KKHKNIGOXrV3dTGbaLbGY4j7EajFXf0bcKFsDyJ0Sg,1409
|
|
6
|
+
compass_lib/geojson.py,sha256=yROsA_J19Afkx-_i3E2TFL2Qi28uAhhAfjLWmpzDoFA,34289
|
|
7
|
+
compass_lib/interface.py,sha256=GcM2yRD39dpy94brTIpJD-Z5DSlmBjpuu6dkTsHpyoI,10576
|
|
8
|
+
compass_lib/io.py,sha256=xU8JYg0gQK6FrpvEQHqXHCBotIWNVJP2Iu1MidgBbQ8,6506
|
|
9
|
+
compass_lib/models.py,sha256=vXQ-kJZVFRmUsNV3-8bsdTC48hLmkuoOIsIco8_al9Y,7036
|
|
10
|
+
compass_lib/validation.py,sha256=tfLn3MNblpQ7vb0TDeimEQV_zeNkLMT8NY0xCdUQFOo,2176
|
|
11
|
+
compass_lib/commands/__init__.py,sha256=kHsx7hB3ivV6wfdRn1KkhlbVPkLcaGsMLXOdgZhKU3Q,79
|
|
12
|
+
compass_lib/commands/convert.py,sha256=RZ3ZFd83yAb2pgso6oaRRtHjEHMSojsaoKmo4RX6fcs,8501
|
|
13
|
+
compass_lib/commands/encrypt.py,sha256=dD5IspbWPnjFjiFDa5RBkfgrQzG_5VglPKGqIOk_Uf8,3189
|
|
14
|
+
compass_lib/commands/geojson.py,sha256=kRgukxnGJIFvFod9odJFaMBZE6BK0I3polNZyBx9G9s,3554
|
|
15
|
+
compass_lib/commands/main.py,sha256=rce8oDfTwZdR5oKVQggNeGn8geIldPRbjvqRucwnFAI,775
|
|
16
|
+
compass_lib/plot/__init__.py,sha256=YXIKLAJSmHtIxiZNxCkDJLR3ud8Dgeea7pKrchB_NtY,953
|
|
17
|
+
compass_lib/plot/models.py,sha256=OEbSibClG95Jo-rDET2chAcfuE0qLsfnj522WW3jmtY,7815
|
|
18
|
+
compass_lib/plot/parser.py,sha256=K3YK-CVXCxAUhLQpi95CajPvWCWOwv41d05Whylysxg,19294
|
|
19
|
+
compass_lib/project/__init__.py,sha256=qdYdBCdEP669a4zT3r7xHm6qDGquf52hkeMyKzXMx_c,1312
|
|
20
|
+
compass_lib/project/format.py,sha256=uxSNG97yzwfUZpV7uYS8n6q0cZEzfSuKi8igBhZwKGY,5033
|
|
21
|
+
compass_lib/project/models.py,sha256=Z3qDggeEV_SEoO5ky5Wjrc7ZmyNdxnTSolNSIpoyQb0,15033
|
|
22
|
+
compass_lib/project/parser.py,sha256=YV3_qUMNbd_enql_5BKcU8jithltTzSmdhbgpSBNvIU,21733
|
|
23
|
+
compass_lib/survey/__init__.py,sha256=9BfBbW0luUr1ldNI4joD1WuZpTH7wuQ2lsdyMw-ppYY,784
|
|
24
|
+
compass_lib/survey/format.py,sha256=L5DPPe3riTknro2a_V70n-jkdJOkp9wK6TX0KbMjrPM,8655
|
|
25
|
+
compass_lib/survey/models.py,sha256=YRch52D0kBf9SxQ5RIhbvIy09-Yt2AtNeNRugJd7K_c,5258
|
|
26
|
+
compass_lib/survey/parser.py,sha256=xvBi3jr1Hgq3rzRzWTYJFQaAHpv7jFtD-QBeyhmAjhM,27402
|
|
27
|
+
compass_lib-0.0.3.dist-info/entry_points.txt,sha256=VhEdfsIVxwPerPEyPYms1IIJ6r4UOeCgPagV2BMzUd8,216
|
|
28
|
+
compass_lib-0.0.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
29
|
+
compass_lib-0.0.3.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
30
|
+
compass_lib-0.0.3.dist-info/METADATA,sha256=9oGhJp4nt62bUfzsDLYJgujs7c-kVELSR7Nc1k1kEv8,2393
|
|
31
|
+
compass_lib-0.0.3.dist-info/RECORD,,
|
compass_lib/encoding.py
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import datetime
|
|
4
|
-
import json
|
|
5
|
-
import uuid
|
|
6
|
-
from dataclasses import asdict
|
|
7
|
-
from dataclasses import is_dataclass
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class EnhancedJSONEncoder(json.JSONEncoder):
|
|
11
|
-
def default(self, obj):
|
|
12
|
-
from compass_lib.parser import ShotFlag # noqa: PLC0415
|
|
13
|
-
|
|
14
|
-
match obj:
|
|
15
|
-
case datetime.date():
|
|
16
|
-
return obj.isoformat()
|
|
17
|
-
|
|
18
|
-
case ShotFlag():
|
|
19
|
-
return obj.value
|
|
20
|
-
|
|
21
|
-
case uuid.UUID():
|
|
22
|
-
return str(obj)
|
|
23
|
-
|
|
24
|
-
if is_dataclass(obj):
|
|
25
|
-
return asdict(obj)
|
|
26
|
-
|
|
27
|
-
return super().default(obj)
|
compass_lib/parser.py
DELETED
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import contextlib
|
|
4
|
-
import datetime
|
|
5
|
-
import re
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
9
|
-
|
|
10
|
-
from compass_lib.constants import COMPASS_DATE_COMMENT_RE
|
|
11
|
-
from compass_lib.constants import COMPASS_END_OF_FILE
|
|
12
|
-
from compass_lib.constants import COMPASS_SECTION_NAME_RE
|
|
13
|
-
from compass_lib.constants import COMPASS_SECTION_SEPARATOR
|
|
14
|
-
from compass_lib.constants import COMPASS_SECTION_SPLIT_RE
|
|
15
|
-
from compass_lib.constants import COMPASS_SHOT_FLAGS_RE
|
|
16
|
-
from compass_lib.enums import CompassFileType
|
|
17
|
-
from compass_lib.enums import ShotFlag
|
|
18
|
-
from compass_lib.models import Survey
|
|
19
|
-
from compass_lib.models import SurveySection
|
|
20
|
-
from compass_lib.models import SurveyShot
|
|
21
|
-
|
|
22
|
-
if TYPE_CHECKING:
|
|
23
|
-
from typing_extensions import Self
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@dataclass
|
|
27
|
-
class CompassDataRow:
|
|
28
|
-
"""Basic Dataclass that represent one row of 'data' from the DAT file.
|
|
29
|
-
This contains no validation logic, the validation is being performed by
|
|
30
|
-
the PyDantic class: `ShotData`.
|
|
31
|
-
The sole purpose of this class is to aggregate the parsing logic."""
|
|
32
|
-
|
|
33
|
-
from_id: str
|
|
34
|
-
to_id: str
|
|
35
|
-
length: float
|
|
36
|
-
azimuth: float
|
|
37
|
-
inclination: float
|
|
38
|
-
left: float
|
|
39
|
-
up: float
|
|
40
|
-
down: float
|
|
41
|
-
right: float
|
|
42
|
-
|
|
43
|
-
# optional attributes
|
|
44
|
-
azimuth2: float = 0.0
|
|
45
|
-
inclination2: float = 0.0
|
|
46
|
-
flags: str | None = None
|
|
47
|
-
comment: str | None = None
|
|
48
|
-
|
|
49
|
-
@classmethod
|
|
50
|
-
def from_str_data(cls, str_data: str, header_row: str) -> Self:
|
|
51
|
-
shot_data = str_data.split(maxsplit=9)
|
|
52
|
-
|
|
53
|
-
instance = cls(*shot_data[:9])
|
|
54
|
-
|
|
55
|
-
def split1_str(val: str) -> tuple[str]:
|
|
56
|
-
"""
|
|
57
|
-
Splits the input string into at most two parts.
|
|
58
|
-
|
|
59
|
-
Args:
|
|
60
|
-
val (str): The string to be split.
|
|
61
|
-
|
|
62
|
-
Returns:
|
|
63
|
-
tuple[str]: A tuple containing the first part of the split string and
|
|
64
|
-
the second part if it exists, otherwise None.
|
|
65
|
-
|
|
66
|
-
Raises:
|
|
67
|
-
ValueError: If the input string is None.
|
|
68
|
-
"""
|
|
69
|
-
if val is None:
|
|
70
|
-
raise ValueError("Received a NoneValue.")
|
|
71
|
-
|
|
72
|
-
rslt = val.split(maxsplit=1)
|
|
73
|
-
if len(rslt) == 1:
|
|
74
|
-
return rslt[0], None
|
|
75
|
-
return rslt
|
|
76
|
-
|
|
77
|
-
with contextlib.suppress(IndexError):
|
|
78
|
-
optional_data = shot_data[9]
|
|
79
|
-
|
|
80
|
-
if "AZM2" in header_row:
|
|
81
|
-
instance.azimuth2, optional_data = split1_str(optional_data)
|
|
82
|
-
|
|
83
|
-
if "INC2" in header_row:
|
|
84
|
-
instance.inclination2, optional_data = split1_str(optional_data)
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
all(x in header_row for x in ["FLAGS", "COMMENTS"])
|
|
88
|
-
and optional_data is not None
|
|
89
|
-
):
|
|
90
|
-
flags_comment = optional_data
|
|
91
|
-
|
|
92
|
-
_, flag_str, comment = re.search(
|
|
93
|
-
COMPASS_SHOT_FLAGS_RE, flags_comment
|
|
94
|
-
).groups()
|
|
95
|
-
|
|
96
|
-
instance.comment = comment.strip() if comment != "" else None
|
|
97
|
-
|
|
98
|
-
instance.flags = (
|
|
99
|
-
[ShotFlag._value2member_map_[f] for f in flag_str]
|
|
100
|
-
if flag_str
|
|
101
|
-
else None
|
|
102
|
-
)
|
|
103
|
-
if instance.flags is not None:
|
|
104
|
-
instance.flags = sorted(set(instance.flags), key=lambda f: f.value)
|
|
105
|
-
|
|
106
|
-
# Input Normalization
|
|
107
|
-
instance.azimuth = float(instance.azimuth)
|
|
108
|
-
instance.azimuth = instance.azimuth % 360.0 if instance.azimuth >= 0 else 0.0
|
|
109
|
-
|
|
110
|
-
instance.azimuth2 = float(instance.azimuth2)
|
|
111
|
-
instance.azimuth2 = instance.azimuth2 % 360.0 if instance.azimuth2 >= 0 else 0.0
|
|
112
|
-
|
|
113
|
-
return instance
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
class CompassParser:
|
|
117
|
-
def __init__(self, *args, **kwargs) -> None:
|
|
118
|
-
raise NotImplementedError(
|
|
119
|
-
"This class is not meant to be instantiated directly."
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
@classmethod
|
|
123
|
-
def load_dat_file(cls, filepath: str) -> Survey:
|
|
124
|
-
filepath = Path(filepath)
|
|
125
|
-
|
|
126
|
-
if not filepath.is_file():
|
|
127
|
-
raise FileNotFoundError(f"File not found: {filepath}")
|
|
128
|
-
|
|
129
|
-
# Ensure at least that the file type is valid
|
|
130
|
-
with Path(filepath).open(mode="r", encoding="windows-1252") as f:
|
|
131
|
-
# Skip all the comments
|
|
132
|
-
file_content = "".join(
|
|
133
|
-
[line for line in f.readlines() if not line.startswith("/")]
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
file_content = file_content.split(COMPASS_END_OF_FILE, maxsplit=1)[0]
|
|
137
|
-
raw_sections = [
|
|
138
|
-
section.rstrip()
|
|
139
|
-
for section in re.split(COMPASS_SECTION_SPLIT_RE, file_content)
|
|
140
|
-
if section.rstrip() != ""
|
|
141
|
-
]
|
|
142
|
-
|
|
143
|
-
try:
|
|
144
|
-
return cls._parse_dat_file(raw_sections)
|
|
145
|
-
except (UnicodeDecodeError, ValueError, IndexError, TypeError) as e:
|
|
146
|
-
raise ValueError(f"Failed to parse file: `{filepath}`") from e
|
|
147
|
-
|
|
148
|
-
@classmethod
|
|
149
|
-
def _parse_date(cls, date_str: str) -> datetime.date:
|
|
150
|
-
for date_format in ["%m %d %Y", "%m %d %y", "%d %m %Y", "%d %m %y"]:
|
|
151
|
-
try:
|
|
152
|
-
return datetime.datetime.strptime(date_str, date_format).date()
|
|
153
|
-
except ValueError: # noqa: PERF203
|
|
154
|
-
continue
|
|
155
|
-
raise ValueError("Unknown date format: `%s`", date_str)
|
|
156
|
-
|
|
157
|
-
@classmethod
|
|
158
|
-
def _parse_dat_file(cls, raw_sections: list[str]) -> Survey:
|
|
159
|
-
survey = Survey(cave_name=raw_sections[0].split("\n", maxsplit=1)[0].strip())
|
|
160
|
-
|
|
161
|
-
for raw_section in raw_sections:
|
|
162
|
-
section_data_iter = iter(raw_section.splitlines())
|
|
163
|
-
|
|
164
|
-
# Note: not used
|
|
165
|
-
# cave_name = next(section_data_iter)
|
|
166
|
-
_ = next(section_data_iter)
|
|
167
|
-
|
|
168
|
-
# -------------- Survey Name -------------- #
|
|
169
|
-
input_str = next(section_data_iter)
|
|
170
|
-
if (match := COMPASS_SECTION_NAME_RE.match(input_str)) is None:
|
|
171
|
-
raise ValueError("Compass section name not found: `%s`", input_str)
|
|
172
|
-
|
|
173
|
-
survey_name = match.group("section_name").strip()
|
|
174
|
-
|
|
175
|
-
# -------------- Survey Date & Comment -------------- #
|
|
176
|
-
input_str = next(section_data_iter).replace("\t", " ")
|
|
177
|
-
if (match := COMPASS_DATE_COMMENT_RE.match(input_str)) is None:
|
|
178
|
-
raise ValueError(
|
|
179
|
-
"Compass date and comment name not found: `%s`", input_str
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
survey_date = (
|
|
183
|
-
cls._parse_date(match.group("date"))
|
|
184
|
-
if match.group("date") != "None"
|
|
185
|
-
else None
|
|
186
|
-
)
|
|
187
|
-
section_comment = (
|
|
188
|
-
match.group("comment").strip() if match.group("comment") else ""
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
# -------------- Surveyors -------------- #
|
|
192
|
-
if (surveyor_header := next(section_data_iter).strip()) != "SURVEY TEAM:":
|
|
193
|
-
raise ValueError("Unknown surveyor string: `%s`", surveyor_header)
|
|
194
|
-
surveyors = next(section_data_iter).rstrip(";, ").rstrip()
|
|
195
|
-
|
|
196
|
-
# -------------- Optional Data -------------- #
|
|
197
|
-
|
|
198
|
-
optional_data = next(section_data_iter).split()
|
|
199
|
-
declination_str = format_str = None
|
|
200
|
-
|
|
201
|
-
correct_A = correct_B = correct_C = correct2_A = correct2_B = 0.0
|
|
202
|
-
discovery_date = survey_date
|
|
203
|
-
|
|
204
|
-
with contextlib.suppress(IndexError, ValueError):
|
|
205
|
-
_header, declination_str = optional_data[0:2]
|
|
206
|
-
_header, format_str = optional_data[2:4]
|
|
207
|
-
_header, correct_A, correct_B, correct_C = optional_data[4:8]
|
|
208
|
-
_header, correct2_A, correct2_B = optional_data[8:11]
|
|
209
|
-
_header, d_month, d_day, d_year = optional_data[11:15]
|
|
210
|
-
discovery_date = cls._parse_date(f"{d_month} {d_day} {d_year}")
|
|
211
|
-
|
|
212
|
-
# -------------- Skip Rows -------------- #
|
|
213
|
-
_ = next(section_data_iter) # empty row
|
|
214
|
-
header_row = next(section_data_iter)
|
|
215
|
-
_ = next(section_data_iter) # empty row
|
|
216
|
-
|
|
217
|
-
# -------------- Section Shots -------------- #
|
|
218
|
-
|
|
219
|
-
shots = []
|
|
220
|
-
|
|
221
|
-
with contextlib.suppress(StopIteration):
|
|
222
|
-
while shot_str := next(section_data_iter):
|
|
223
|
-
shot_data = CompassDataRow.from_str_data(
|
|
224
|
-
str_data=shot_str, header_row=header_row
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
shots.append(
|
|
228
|
-
SurveyShot(
|
|
229
|
-
from_id=shot_data.from_id,
|
|
230
|
-
to_id=shot_data.to_id,
|
|
231
|
-
azimuth=float(shot_data.azimuth),
|
|
232
|
-
inclination=float(shot_data.inclination),
|
|
233
|
-
length=float(shot_data.length),
|
|
234
|
-
# Optional Values
|
|
235
|
-
comment=shot_data.comment,
|
|
236
|
-
flags=shot_data.flags,
|
|
237
|
-
azimuth2=float(shot_data.azimuth2),
|
|
238
|
-
inclination2=float(shot_data.inclination2),
|
|
239
|
-
# LRUD
|
|
240
|
-
left=float(shot_data.left),
|
|
241
|
-
right=float(shot_data.right),
|
|
242
|
-
up=float(shot_data.up),
|
|
243
|
-
down=float(shot_data.down),
|
|
244
|
-
)
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
survey.sections.append(
|
|
248
|
-
SurveySection(
|
|
249
|
-
name=survey_name,
|
|
250
|
-
comment=section_comment,
|
|
251
|
-
correction=(float(correct_A), float(correct_B), float(correct_C)),
|
|
252
|
-
correction2=(float(correct2_A), float(correct2_B)),
|
|
253
|
-
survey_date=survey_date,
|
|
254
|
-
discovery_date=discovery_date,
|
|
255
|
-
declination=float(declination_str),
|
|
256
|
-
format=format_str if format_str is not None else "DDDDUDLRLADN",
|
|
257
|
-
shots=shots,
|
|
258
|
-
surveyors=surveyors,
|
|
259
|
-
)
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
return survey
|
|
263
|
-
|
|
264
|
-
# =================== Export Formats =================== #
|
|
265
|
-
|
|
266
|
-
# @classmethod
|
|
267
|
-
# def calculate_depth(
|
|
268
|
-
# self, filepath: str | Path | None = None, include_depth: bool = False
|
|
269
|
-
# ) -> str:
|
|
270
|
-
# data = self.data.model_dump()
|
|
271
|
-
|
|
272
|
-
# all_shots = [
|
|
273
|
-
# shot for section in data["sections"] for shot in section["shots"]
|
|
274
|
-
# ]
|
|
275
|
-
|
|
276
|
-
# if not include_depth:
|
|
277
|
-
# for shot in all_shots:
|
|
278
|
-
# del shot["depth"]
|
|
279
|
-
|
|
280
|
-
# else:
|
|
281
|
-
# # create an index of all the shots by "ID"
|
|
282
|
-
# # use a copy to avoid data corruption.
|
|
283
|
-
# shot_by_origins = defaultdict(list)
|
|
284
|
-
# shot_by_destinations = defaultdict(list)
|
|
285
|
-
# for shot in all_shots:
|
|
286
|
-
# shot_by_origins[shot["from_id"]].append(shot)
|
|
287
|
-
# shot_by_destinations[shot["to_id"]].append(shot)
|
|
288
|
-
|
|
289
|
-
# origin_keys = set(shot_by_origins.keys())
|
|
290
|
-
# destination_keys = set(shot_by_destinations.keys())
|
|
291
|
-
|
|
292
|
-
# # Finding the "origin stations" - aka. stations with no incoming
|
|
293
|
-
# # shots. They are assumed at depth 0.0
|
|
294
|
-
# origin_stations = set()
|
|
295
|
-
# for shot_key in origin_keys:
|
|
296
|
-
# if shot_key in destination_keys:
|
|
297
|
-
# continue
|
|
298
|
-
# origin_stations.add(shot_key)
|
|
299
|
-
|
|
300
|
-
# processing_queue = OrderedQueue()
|
|
301
|
-
|
|
302
|
-
# def collect_downstream_stations(target: str) -> list[str]:
|
|
303
|
-
# if target in processing_queue:
|
|
304
|
-
# return
|
|
305
|
-
|
|
306
|
-
# processing_queue.add(target, value=None, fail_if_present=True)
|
|
307
|
-
# direct_shots = shot_by_origins[target]
|
|
308
|
-
|
|
309
|
-
# for shot in direct_shots:
|
|
310
|
-
# processing_queue.add(
|
|
311
|
-
# shot["from_id"], value=None, fail_if_present=False
|
|
312
|
-
# )
|
|
313
|
-
# if (next_shot := shot["to_id"]) not in processing_queue:
|
|
314
|
-
# collect_downstream_stations(next_shot)
|
|
315
|
-
|
|
316
|
-
# for station in sorted(origin_stations):
|
|
317
|
-
# collect_downstream_stations(station)
|
|
318
|
-
|
|
319
|
-
# def calculate_depth(
|
|
320
|
-
# target: str, fail_if_unknown: bool = False
|
|
321
|
-
# ) -> float | None:
|
|
322
|
-
# if target in origin_stations:
|
|
323
|
-
# return 0.0
|
|
324
|
-
|
|
325
|
-
# if (depth := processing_queue[target]) is not None:
|
|
326
|
-
# return depth
|
|
327
|
-
|
|
328
|
-
# if fail_if_unknown:
|
|
329
|
-
# return None
|
|
330
|
-
|
|
331
|
-
# for shot in shot_by_destinations[target]:
|
|
332
|
-
# start_depth = calculate_depth(
|
|
333
|
-
# shot["from_id"], fail_if_unknown=True
|
|
334
|
-
# )
|
|
335
|
-
# if start_depth is not None:
|
|
336
|
-
# break
|
|
337
|
-
# else:
|
|
338
|
-
# raise RuntimeError("None of the previous shot has a known depth")
|
|
339
|
-
|
|
340
|
-
# vertical_delta = math.cos(
|
|
341
|
-
# math.radians(90 + float(shot["inclination"]))
|
|
342
|
-
# ) * float(shot["length"])
|
|
343
|
-
|
|
344
|
-
# return round(start_depth + vertical_delta, ndigits=4)
|
|
345
|
-
|
|
346
|
-
# for shot in processing_queue:
|
|
347
|
-
# processing_queue[shot] = calculate_depth(shot)
|
|
348
|
-
|
|
349
|
-
# for shot in all_shots:
|
|
350
|
-
# shot["depth"] = round(processing_queue[shot["to_id"]], ndigits=1)
|
|
351
|
-
|
|
352
|
-
@classmethod
|
|
353
|
-
def export_to_dat(cls, survey: Survey, filepath: Path | str) -> None:
|
|
354
|
-
filepath = Path(filepath)
|
|
355
|
-
|
|
356
|
-
filetype = CompassFileType.from_path(filepath)
|
|
357
|
-
|
|
358
|
-
if filetype != CompassFileType.DAT:
|
|
359
|
-
raise TypeError(
|
|
360
|
-
f"Unsupported fileformat: `{filetype.name}`. "
|
|
361
|
-
f"Expected: `{CompassFileType.DAT.name}`"
|
|
362
|
-
)
|
|
363
|
-
|
|
364
|
-
with filepath.open(mode="w", encoding="windows-1252") as f:
|
|
365
|
-
for section in survey.sections:
|
|
366
|
-
# Section Header
|
|
367
|
-
f.write(f"{survey.cave_name}\n")
|
|
368
|
-
f.write(f"SURVEY NAME: {section.name}\n")
|
|
369
|
-
f.write(
|
|
370
|
-
"".join(
|
|
371
|
-
(
|
|
372
|
-
"SURVEY DATE: ",
|
|
373
|
-
section.survey_date.strftime("%m %-d %Y")
|
|
374
|
-
if section.survey_date
|
|
375
|
-
else "None",
|
|
376
|
-
" ",
|
|
377
|
-
)
|
|
378
|
-
)
|
|
379
|
-
)
|
|
380
|
-
f.write(f"COMMENT:{section.comment}\n")
|
|
381
|
-
f.write(f"SURVEY TEAM:\n{section.surveyors}\n")
|
|
382
|
-
f.write(f"DECLINATION: {section.declination:>7.02f} ")
|
|
383
|
-
f.write(f"FORMAT: {section.format} ")
|
|
384
|
-
f.write(
|
|
385
|
-
f"CORRECTIONS: {' '.join(f'{nbr:.02f}' for nbr in section.correction)} " # noqa: E501
|
|
386
|
-
)
|
|
387
|
-
f.write(
|
|
388
|
-
f"CORRECTIONS2: {' '.join(f'{nbr:.02f}' for nbr in section.correction2)} " # noqa: E501
|
|
389
|
-
)
|
|
390
|
-
f.write(
|
|
391
|
-
"".join(
|
|
392
|
-
(
|
|
393
|
-
"DISCOVERY: ",
|
|
394
|
-
section.discovery_date.strftime("%m %-d %Y")
|
|
395
|
-
if section.discovery_date
|
|
396
|
-
else "None",
|
|
397
|
-
"\n\n",
|
|
398
|
-
)
|
|
399
|
-
)
|
|
400
|
-
)
|
|
401
|
-
|
|
402
|
-
# Shots - Header
|
|
403
|
-
f.write(" FROM TO LENGTH BEARING INC")
|
|
404
|
-
f.write(" LEFT UP DOWN RIGHT")
|
|
405
|
-
f.write(" AZM2 INC2 FLAGS COMMENTS\n\n")
|
|
406
|
-
|
|
407
|
-
# Shots - Data
|
|
408
|
-
for shot in section.shots:
|
|
409
|
-
f.write(f"{shot.from_id: >12} ")
|
|
410
|
-
f.write(f"{shot.to_id: >12} ")
|
|
411
|
-
f.write(f"{shot.length:8.2f} ")
|
|
412
|
-
f.write(f"{shot.azimuth:8.2f} ")
|
|
413
|
-
f.write(f"{shot.inclination:8.3f} ")
|
|
414
|
-
f.write(f"{shot.left:8.2f} ")
|
|
415
|
-
f.write(f"{shot.up:8.2f} ")
|
|
416
|
-
f.write(f"{shot.down:8.2f} ")
|
|
417
|
-
f.write(f"{shot.right:8.2f} ")
|
|
418
|
-
f.write(f"{shot.azimuth2:8.2f} ")
|
|
419
|
-
f.write(f"{shot.inclination2:8.3f}")
|
|
420
|
-
if shot.flags is not None:
|
|
421
|
-
escaped_start_token = str(ShotFlag.__start_token__).replace(
|
|
422
|
-
"\\", ""
|
|
423
|
-
)
|
|
424
|
-
f.write(f" {escaped_start_token}")
|
|
425
|
-
f.write("".join([flag.value for flag in shot.flags]))
|
|
426
|
-
f.write(ShotFlag.__end_token__)
|
|
427
|
-
if shot.comment is not None:
|
|
428
|
-
f.write(f" {shot.comment}")
|
|
429
|
-
f.write("\n")
|
|
430
|
-
|
|
431
|
-
# End of Section - Form_feed: https://www.ascii-code.com/12
|
|
432
|
-
f.write(f"{COMPASS_SECTION_SEPARATOR}\n")
|
|
433
|
-
|
|
434
|
-
# End of File - Substitute: https://www.ascii-code.com/26
|
|
435
|
-
f.write(f"{COMPASS_END_OF_FILE}\n")
|
compass_lib/utils.py
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from collections import OrderedDict
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class OrderedQueue(OrderedDict):
|
|
8
|
-
def add(self, key: Any, value: Any, fail_if_present: bool = False) -> None:
|
|
9
|
-
if key in self and fail_if_present:
|
|
10
|
-
raise KeyError(f"The key `{key}` is already present: {self[key]=}")
|
|
11
|
-
|
|
12
|
-
self[key] = value
|
|
13
|
-
|
|
14
|
-
def remove(self, key: Any) -> None:
|
|
15
|
-
del self[key]
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
compass_lib/__init__.py,sha256=XFhyd7zyNPgBjFn2BggtlX_DYavzBrNB1BFolWoIzu0,97
|
|
2
|
-
compass_lib/constants.py,sha256=uPH582VZIwMprlsoiv8wW7fA5vR3mvUHx1aga-qV_ew,1227
|
|
3
|
-
compass_lib/encoding.py,sha256=_7hv4JlIVyliD8JKSocTc_bd5mofrmgJTQXQA2nclRs,602
|
|
4
|
-
compass_lib/enums.py,sha256=xUdBGlCs8mTF16CBvhvtb49hp1yS4U15eR0jpVfc9sk,3127
|
|
5
|
-
compass_lib/models.py,sha256=12eKJ2oLV3TpQ44P6JB4iVjtkkRLM0-zIxSbFJDJ8BE,3984
|
|
6
|
-
compass_lib/parser.py,sha256=Pi-UZc-U6Nx1HlfvoxePhJx--5mZO2PZ79Tv6kENzpI,16809
|
|
7
|
-
compass_lib/utils.py,sha256=bUGNttxtLvPBcvtYMIecA0-h5TTgoTI0PhIOZ6upbIg,424
|
|
8
|
-
compass_lib/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
compass_lib/commands/convert.py,sha256=qAu5FGU1kGHqc3n-Nk5uZ3D-u1X8NzFVswwi-irosao,1498
|
|
10
|
-
compass_lib/commands/encrypt.py,sha256=mH_X_TTElK6QsIv2YOYVU2NnaA3_6q99QqM2_pndXn0,2377
|
|
11
|
-
compass_lib/commands/main.py,sha256=qunRnE1X19zLlevUwZ28ohIJFkNOP7Y5Ac0HNktGwWQ,771
|
|
12
|
-
compass_lib-0.0.2.dist-info/entry_points.txt,sha256=Il3xmB2Aqrat1MbNKr5opeVmAjMynU-gmbMWeF-toI0,167
|
|
13
|
-
compass_lib-0.0.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
14
|
-
compass_lib-0.0.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
15
|
-
compass_lib-0.0.2.dist-info/METADATA,sha256=E32zTQU3h6NDCoGec7Ex3BW3d5U2QpqGhJ8v1wSDr1g,2305
|
|
16
|
-
compass_lib-0.0.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|