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.
@@ -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.2
4
- Summary: "A library to read Compass Survey files"
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,<9.0.0 ; extra == "test"
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,,
@@ -1,6 +1,7 @@
1
- [compass.actions]
1
+ [compass_lib.actions]
2
2
  convert=compass_lib.commands.convert:convert
3
3
  encrypt=compass_lib.commands.encrypt:encrypt
4
+ geojson=compass_lib.commands.geojson:geojson
4
5
 
5
6
  [console_scripts]
6
7
  compass=compass_lib.commands.main:main
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,,