compass-lib 0.0.1__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 +115 -3
- compass_lib/commands/__init__.py +2 -1
- compass_lib/commands/convert.py +225 -32
- compass_lib/commands/encrypt.py +115 -0
- compass_lib/commands/geojson.py +118 -0
- compass_lib/commands/main.py +4 -2
- compass_lib/constants.py +84 -0
- compass_lib/enums.py +309 -65
- 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 +251 -0
- 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.3.dist-info/METADATA +60 -0
- compass_lib-0.0.3.dist-info/RECORD +31 -0
- {compass_lib-0.0.1.dist-info → compass_lib-0.0.3.dist-info}/WHEEL +1 -3
- compass_lib-0.0.3.dist-info/entry_points.txt +8 -0
- compass_lib/parser.py +0 -282
- compass_lib/section.py +0 -18
- compass_lib/shot.py +0 -21
- compass_lib-0.0.1.dist-info/METADATA +0 -268
- compass_lib-0.0.1.dist-info/RECORD +0 -14
- compass_lib-0.0.1.dist-info/entry_points.txt +0 -5
- compass_lib-0.0.1.dist-info/top_level.txt +0 -1
- {compass_lib-0.0.1.dist-info → compass_lib-0.0.3.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Parser for Compass .PLT plot files.
|
|
3
|
+
|
|
4
|
+
This module implements the parser for reading Compass plot files,
|
|
5
|
+
which contain computed 3D coordinates for cave visualization.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from datetime import date
|
|
10
|
+
from decimal import Decimal
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from compass_lib.constants import ASCII_ENCODING
|
|
14
|
+
from compass_lib.constants import NULL_LRUD_VALUES
|
|
15
|
+
from compass_lib.enums import DrawOperation
|
|
16
|
+
from compass_lib.enums import Severity
|
|
17
|
+
from compass_lib.errors import CompassParseError
|
|
18
|
+
from compass_lib.errors import SourceLocation
|
|
19
|
+
from compass_lib.models import Bounds
|
|
20
|
+
from compass_lib.models import Location
|
|
21
|
+
from compass_lib.plot.models import BeginFeatureCommand
|
|
22
|
+
from compass_lib.plot.models import BeginSectionCommand
|
|
23
|
+
from compass_lib.plot.models import BeginSurveyCommand
|
|
24
|
+
from compass_lib.plot.models import CaveBoundsCommand
|
|
25
|
+
from compass_lib.plot.models import CompassPlotCommand
|
|
26
|
+
from compass_lib.plot.models import DatumCommand
|
|
27
|
+
from compass_lib.plot.models import DrawSurveyCommand
|
|
28
|
+
from compass_lib.plot.models import FeatureCommand
|
|
29
|
+
from compass_lib.plot.models import SurveyBoundsCommand
|
|
30
|
+
from compass_lib.plot.models import UtmZoneCommand
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CompassPlotParser:
|
|
34
|
+
"""Parser for Compass .PLT plot files.
|
|
35
|
+
|
|
36
|
+
This parser reads Compass plot files and returns a list of
|
|
37
|
+
CompassPlotCommand objects. Errors are collected rather than thrown,
|
|
38
|
+
allowing partial parsing of malformed files.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
errors: List of parsing errors and warnings encountered
|
|
42
|
+
commands: List of successfully parsed commands
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
# Regex patterns
|
|
46
|
+
UINT_PATTERN = re.compile(r"[1-9]\d*")
|
|
47
|
+
NUMBER_PATTERN = re.compile(r"[-+]?\d+\.?\d*(?:[eE][-+]?\d+)?")
|
|
48
|
+
NON_WHITESPACE = re.compile(r"\S+")
|
|
49
|
+
|
|
50
|
+
def __init__(self) -> None:
|
|
51
|
+
"""Initialize the parser."""
|
|
52
|
+
self.errors: list[CompassParseError] = []
|
|
53
|
+
self.commands: list[CompassPlotCommand] = []
|
|
54
|
+
self._source: str = "<string>"
|
|
55
|
+
|
|
56
|
+
def _add_error(
|
|
57
|
+
self,
|
|
58
|
+
message: str,
|
|
59
|
+
text: str = "",
|
|
60
|
+
line: int = 0,
|
|
61
|
+
column: int = 0,
|
|
62
|
+
) -> None:
|
|
63
|
+
"""Add an error to the error list."""
|
|
64
|
+
self.errors.append(
|
|
65
|
+
CompassParseError(
|
|
66
|
+
severity=Severity.ERROR,
|
|
67
|
+
message=message,
|
|
68
|
+
location=SourceLocation(
|
|
69
|
+
source=self._source,
|
|
70
|
+
line=line,
|
|
71
|
+
column=column,
|
|
72
|
+
text=text,
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def _add_warning(
|
|
78
|
+
self,
|
|
79
|
+
message: str,
|
|
80
|
+
text: str = "",
|
|
81
|
+
line: int = 0,
|
|
82
|
+
column: int = 0,
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Add a warning to the error list."""
|
|
85
|
+
self.errors.append(
|
|
86
|
+
CompassParseError(
|
|
87
|
+
severity=Severity.WARNING,
|
|
88
|
+
message=message,
|
|
89
|
+
location=SourceLocation(
|
|
90
|
+
source=self._source,
|
|
91
|
+
line=line,
|
|
92
|
+
column=column,
|
|
93
|
+
text=text,
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def parse_file(self, path: Path) -> list[CompassPlotCommand]:
|
|
99
|
+
"""Parse a plot file.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
path: Path to the .PLT file
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
List of parsed commands
|
|
106
|
+
"""
|
|
107
|
+
self._source = str(path)
|
|
108
|
+
with path.open(mode="r", encoding=ASCII_ENCODING, errors="replace") as f:
|
|
109
|
+
return self.parse_lines(f, str(path))
|
|
110
|
+
|
|
111
|
+
def parse_string(
|
|
112
|
+
self,
|
|
113
|
+
data: str,
|
|
114
|
+
source: str = "<string>",
|
|
115
|
+
) -> list[CompassPlotCommand]:
|
|
116
|
+
"""Parse plot data from a string.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
data: Plot file content
|
|
120
|
+
source: Source identifier for error messages
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
List of parsed commands
|
|
124
|
+
"""
|
|
125
|
+
self._source = source
|
|
126
|
+
lines = data.split("\n")
|
|
127
|
+
return self._parse_lines_list(lines, source)
|
|
128
|
+
|
|
129
|
+
def parse_lines(
|
|
130
|
+
self,
|
|
131
|
+
file_obj,
|
|
132
|
+
source: str = "<string>",
|
|
133
|
+
) -> list[CompassPlotCommand]:
|
|
134
|
+
"""Parse plot data from a file object.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
file_obj: File-like object to read from
|
|
138
|
+
source: Source identifier for error messages
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
List of parsed commands
|
|
142
|
+
"""
|
|
143
|
+
self._source = source
|
|
144
|
+
commands: list[CompassPlotCommand] = []
|
|
145
|
+
|
|
146
|
+
for line_num, line in enumerate(file_obj):
|
|
147
|
+
_line = line.strip()
|
|
148
|
+
if not _line:
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
command = self._parse_command(_line, line_num)
|
|
153
|
+
if command:
|
|
154
|
+
commands.append(command)
|
|
155
|
+
except Exception as e: # noqa: BLE001
|
|
156
|
+
self._add_error(str(e), _line, line_num)
|
|
157
|
+
|
|
158
|
+
self.commands.extend(commands)
|
|
159
|
+
return commands
|
|
160
|
+
|
|
161
|
+
def _parse_lines_list(
|
|
162
|
+
self,
|
|
163
|
+
lines: list[str],
|
|
164
|
+
source: str,
|
|
165
|
+
) -> list[CompassPlotCommand]:
|
|
166
|
+
"""Parse lines from a list."""
|
|
167
|
+
self._source = source
|
|
168
|
+
commands: list[CompassPlotCommand] = []
|
|
169
|
+
|
|
170
|
+
for line_num, line in enumerate(lines):
|
|
171
|
+
_line = line.strip()
|
|
172
|
+
if not _line:
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
command = self._parse_command(_line, line_num)
|
|
177
|
+
if command:
|
|
178
|
+
commands.append(command)
|
|
179
|
+
except Exception as e: # noqa: BLE001
|
|
180
|
+
self._add_error(str(e), _line, line_num)
|
|
181
|
+
|
|
182
|
+
self.commands.extend(commands)
|
|
183
|
+
return commands
|
|
184
|
+
|
|
185
|
+
def _parse_command( # noqa: PLR0911
|
|
186
|
+
self,
|
|
187
|
+
line: str,
|
|
188
|
+
line_num: int,
|
|
189
|
+
) -> CompassPlotCommand | None:
|
|
190
|
+
"""Parse a single command line.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
line: Command line text
|
|
194
|
+
line_num: Line number for error reporting
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Parsed command or None if unknown/invalid
|
|
198
|
+
"""
|
|
199
|
+
if not line:
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
cmd = line[0]
|
|
203
|
+
rest = line[1:]
|
|
204
|
+
|
|
205
|
+
if cmd in ("M", "D"):
|
|
206
|
+
return self._parse_draw_command(cmd, rest, line_num)
|
|
207
|
+
if cmd == "N":
|
|
208
|
+
return self._parse_begin_survey(rest, line_num)
|
|
209
|
+
if cmd == "F":
|
|
210
|
+
return self._parse_begin_feature(rest, line_num)
|
|
211
|
+
if cmd == "S":
|
|
212
|
+
return self._parse_begin_section(rest, line_num)
|
|
213
|
+
if cmd == "L":
|
|
214
|
+
return self._parse_feature_command(rest, line_num)
|
|
215
|
+
if cmd == "X":
|
|
216
|
+
return self._parse_survey_bounds(rest, line_num)
|
|
217
|
+
if cmd == "Z":
|
|
218
|
+
return self._parse_cave_bounds(rest, line_num)
|
|
219
|
+
if cmd == "O":
|
|
220
|
+
return self._parse_datum(rest, line_num)
|
|
221
|
+
if cmd == "G":
|
|
222
|
+
return self._parse_utm_zone(rest, line_num)
|
|
223
|
+
|
|
224
|
+
# Unknown command - ignore silently (many undocumented commands exist)
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
def _parse_number(
|
|
228
|
+
self,
|
|
229
|
+
text: str,
|
|
230
|
+
line_num: int,
|
|
231
|
+
field_name: str,
|
|
232
|
+
) -> float | None:
|
|
233
|
+
"""Parse a number, returning None on failure."""
|
|
234
|
+
text = text.strip()
|
|
235
|
+
try:
|
|
236
|
+
return float(text)
|
|
237
|
+
except ValueError:
|
|
238
|
+
self._add_error(f"invalid {field_name}: {text}", text, line_num)
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
def _parse_lrud(
|
|
242
|
+
self,
|
|
243
|
+
text: str,
|
|
244
|
+
line_num: int,
|
|
245
|
+
field_name: str,
|
|
246
|
+
) -> float | None:
|
|
247
|
+
"""Parse LRUD measurement.
|
|
248
|
+
|
|
249
|
+
Returns None for missing data indicators (negative or 999/999.9).
|
|
250
|
+
"""
|
|
251
|
+
value = self._parse_number(text, line_num, field_name)
|
|
252
|
+
if value is None:
|
|
253
|
+
return None
|
|
254
|
+
|
|
255
|
+
# Null indicators
|
|
256
|
+
if value < 0 or value in NULL_LRUD_VALUES:
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
return value
|
|
260
|
+
|
|
261
|
+
def _parse_date(
|
|
262
|
+
self,
|
|
263
|
+
parts: list[str],
|
|
264
|
+
start_idx: int,
|
|
265
|
+
line_num: int,
|
|
266
|
+
) -> tuple[date | None, int]:
|
|
267
|
+
"""Parse date from parts (month day year).
|
|
268
|
+
|
|
269
|
+
Returns (date, new_index) tuple.
|
|
270
|
+
"""
|
|
271
|
+
if start_idx + 2 >= len(parts):
|
|
272
|
+
self._add_error("incomplete date", "", line_num)
|
|
273
|
+
return None, start_idx
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
month = int(parts[start_idx])
|
|
277
|
+
day = int(parts[start_idx + 1])
|
|
278
|
+
year = int(parts[start_idx + 2])
|
|
279
|
+
|
|
280
|
+
if month < 1 or month > 12:
|
|
281
|
+
self._add_error(
|
|
282
|
+
f"month must be between 1 and 12: {month}", "", line_num
|
|
283
|
+
)
|
|
284
|
+
return None, start_idx + 3
|
|
285
|
+
|
|
286
|
+
if day < 1 or day > 31:
|
|
287
|
+
self._add_error(f"day must be between 1 and 31: {day}", "", line_num)
|
|
288
|
+
return None, start_idx + 3
|
|
289
|
+
|
|
290
|
+
return date(year, month, day), start_idx + 3
|
|
291
|
+
except ValueError as e:
|
|
292
|
+
self._add_error(f"invalid date: {e}", "", line_num)
|
|
293
|
+
return None, start_idx + 3
|
|
294
|
+
|
|
295
|
+
def _parse_draw_command(
|
|
296
|
+
self,
|
|
297
|
+
cmd: str,
|
|
298
|
+
rest: str,
|
|
299
|
+
line_num: int,
|
|
300
|
+
) -> DrawSurveyCommand | None:
|
|
301
|
+
"""Parse M (move) or D (draw) command."""
|
|
302
|
+
operation = DrawOperation.MOVE_TO if cmd == "M" else DrawOperation.LINE_TO
|
|
303
|
+
parts = rest.split()
|
|
304
|
+
|
|
305
|
+
if len(parts) < 3:
|
|
306
|
+
self._add_error(
|
|
307
|
+
"draw command requires at least 3 coordinates",
|
|
308
|
+
rest,
|
|
309
|
+
line_num,
|
|
310
|
+
)
|
|
311
|
+
return None
|
|
312
|
+
|
|
313
|
+
northing = self._parse_number(parts[0], line_num, "northing")
|
|
314
|
+
easting = self._parse_number(parts[1], line_num, "easting")
|
|
315
|
+
vertical = self._parse_number(parts[2], line_num, "vertical")
|
|
316
|
+
|
|
317
|
+
command = DrawSurveyCommand(
|
|
318
|
+
operation=operation,
|
|
319
|
+
location=Location(
|
|
320
|
+
northing=northing,
|
|
321
|
+
easting=easting,
|
|
322
|
+
vertical=vertical,
|
|
323
|
+
),
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Parse subcommands
|
|
327
|
+
idx = 3
|
|
328
|
+
while idx < len(parts):
|
|
329
|
+
subcmd = parts[idx]
|
|
330
|
+
idx += 1
|
|
331
|
+
|
|
332
|
+
if subcmd.startswith("S"):
|
|
333
|
+
# Station name
|
|
334
|
+
command.station_name = subcmd[1:] if len(subcmd) > 1 else None
|
|
335
|
+
if not command.station_name and idx < len(parts):
|
|
336
|
+
command.station_name = parts[idx]
|
|
337
|
+
idx += 1
|
|
338
|
+
|
|
339
|
+
elif subcmd == "P":
|
|
340
|
+
# LRUD: left, up, down, right (in this order for draw commands)
|
|
341
|
+
if idx + 3 < len(parts):
|
|
342
|
+
command.left = self._parse_lrud(parts[idx], line_num, "left")
|
|
343
|
+
idx += 1
|
|
344
|
+
command.up = self._parse_lrud(parts[idx], line_num, "up")
|
|
345
|
+
idx += 1
|
|
346
|
+
command.down = self._parse_lrud(parts[idx], line_num, "down")
|
|
347
|
+
idx += 1
|
|
348
|
+
command.right = self._parse_lrud(parts[idx], line_num, "right")
|
|
349
|
+
idx += 1
|
|
350
|
+
|
|
351
|
+
elif subcmd == "I":
|
|
352
|
+
# Distance from entrance
|
|
353
|
+
if idx < len(parts):
|
|
354
|
+
dist = self._parse_number(
|
|
355
|
+
parts[idx],
|
|
356
|
+
line_num,
|
|
357
|
+
"distance from entrance",
|
|
358
|
+
)
|
|
359
|
+
if dist is not None:
|
|
360
|
+
if dist < 0:
|
|
361
|
+
self._add_warning(
|
|
362
|
+
"distance from entrance is negative",
|
|
363
|
+
parts[idx],
|
|
364
|
+
line_num,
|
|
365
|
+
)
|
|
366
|
+
command.distance_from_entrance = dist
|
|
367
|
+
idx += 1
|
|
368
|
+
# Stop parsing after I (undocumented commands may follow)
|
|
369
|
+
break
|
|
370
|
+
|
|
371
|
+
return command
|
|
372
|
+
|
|
373
|
+
def _parse_feature_command(
|
|
374
|
+
self,
|
|
375
|
+
rest: str,
|
|
376
|
+
line_num: int,
|
|
377
|
+
) -> FeatureCommand | None:
|
|
378
|
+
"""Parse L (feature) command."""
|
|
379
|
+
parts = rest.split()
|
|
380
|
+
|
|
381
|
+
if len(parts) < 3:
|
|
382
|
+
self._add_error(
|
|
383
|
+
"feature command requires at least 3 coordinates",
|
|
384
|
+
rest,
|
|
385
|
+
line_num,
|
|
386
|
+
)
|
|
387
|
+
return None
|
|
388
|
+
|
|
389
|
+
northing = self._parse_number(parts[0], line_num, "northing")
|
|
390
|
+
easting = self._parse_number(parts[1], line_num, "easting")
|
|
391
|
+
vertical = self._parse_number(parts[2], line_num, "vertical")
|
|
392
|
+
|
|
393
|
+
command = FeatureCommand(
|
|
394
|
+
location=Location(
|
|
395
|
+
northing=northing,
|
|
396
|
+
easting=easting,
|
|
397
|
+
vertical=vertical,
|
|
398
|
+
),
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
# Parse subcommands
|
|
402
|
+
idx = 3
|
|
403
|
+
while idx < len(parts):
|
|
404
|
+
subcmd = parts[idx]
|
|
405
|
+
idx += 1
|
|
406
|
+
|
|
407
|
+
if subcmd.startswith("S"):
|
|
408
|
+
# Station name
|
|
409
|
+
command.station_name = subcmd[1:] if len(subcmd) > 1 else None
|
|
410
|
+
if not command.station_name and idx < len(parts):
|
|
411
|
+
command.station_name = parts[idx]
|
|
412
|
+
idx += 1
|
|
413
|
+
|
|
414
|
+
elif subcmd == "P":
|
|
415
|
+
# LRUD: left, right, up, down (different order than draw!)
|
|
416
|
+
if idx + 3 < len(parts):
|
|
417
|
+
command.left = self._parse_lrud(parts[idx], line_num, "left")
|
|
418
|
+
idx += 1
|
|
419
|
+
command.right = self._parse_lrud(parts[idx], line_num, "right")
|
|
420
|
+
idx += 1
|
|
421
|
+
command.up = self._parse_lrud(parts[idx], line_num, "up")
|
|
422
|
+
idx += 1
|
|
423
|
+
command.down = self._parse_lrud(parts[idx], line_num, "down")
|
|
424
|
+
idx += 1
|
|
425
|
+
|
|
426
|
+
elif subcmd == "V":
|
|
427
|
+
# Feature value
|
|
428
|
+
if idx < len(parts):
|
|
429
|
+
try:
|
|
430
|
+
command.value = Decimal(parts[idx])
|
|
431
|
+
except Exception: # noqa: BLE001
|
|
432
|
+
self._add_error(
|
|
433
|
+
f"invalid value: {parts[idx]}",
|
|
434
|
+
parts[idx],
|
|
435
|
+
line_num,
|
|
436
|
+
)
|
|
437
|
+
idx += 1
|
|
438
|
+
|
|
439
|
+
return command
|
|
440
|
+
|
|
441
|
+
def _parse_begin_survey(
|
|
442
|
+
self,
|
|
443
|
+
rest: str,
|
|
444
|
+
line_num: int,
|
|
445
|
+
) -> BeginSurveyCommand:
|
|
446
|
+
"""Parse N (begin survey) command."""
|
|
447
|
+
parts = rest.split()
|
|
448
|
+
|
|
449
|
+
if not parts:
|
|
450
|
+
self._add_error("missing survey name", rest, line_num)
|
|
451
|
+
return BeginSurveyCommand(survey_name="")
|
|
452
|
+
|
|
453
|
+
survey_name = parts[0]
|
|
454
|
+
command = BeginSurveyCommand(survey_name=survey_name)
|
|
455
|
+
|
|
456
|
+
# Parse subcommands
|
|
457
|
+
idx = 1
|
|
458
|
+
while idx < len(parts):
|
|
459
|
+
subcmd = parts[idx]
|
|
460
|
+
idx += 1
|
|
461
|
+
|
|
462
|
+
if subcmd == "D":
|
|
463
|
+
# Date
|
|
464
|
+
parsed_date, idx = self._parse_date(parts, idx, line_num)
|
|
465
|
+
command.date = parsed_date
|
|
466
|
+
|
|
467
|
+
elif subcmd == "C" or subcmd.startswith("C"):
|
|
468
|
+
# Comment (rest of line)
|
|
469
|
+
if subcmd == "C":
|
|
470
|
+
command.comment = " ".join(parts[idx:]).strip()
|
|
471
|
+
else:
|
|
472
|
+
command.comment = (subcmd[1:] + " " + " ".join(parts[idx:])).strip()
|
|
473
|
+
break
|
|
474
|
+
|
|
475
|
+
return command
|
|
476
|
+
|
|
477
|
+
def _parse_begin_section(
|
|
478
|
+
self,
|
|
479
|
+
rest: str,
|
|
480
|
+
line_num: int,
|
|
481
|
+
) -> BeginSectionCommand:
|
|
482
|
+
"""Parse S (begin section) command."""
|
|
483
|
+
# Section name is the rest of the line
|
|
484
|
+
return BeginSectionCommand(section_name=rest.strip())
|
|
485
|
+
|
|
486
|
+
def _parse_begin_feature(
|
|
487
|
+
self,
|
|
488
|
+
rest: str,
|
|
489
|
+
line_num: int,
|
|
490
|
+
) -> BeginFeatureCommand:
|
|
491
|
+
"""Parse F (begin feature) command."""
|
|
492
|
+
parts = rest.split()
|
|
493
|
+
|
|
494
|
+
if not parts:
|
|
495
|
+
self._add_error("missing feature name", rest, line_num)
|
|
496
|
+
return BeginFeatureCommand(feature_name="")
|
|
497
|
+
|
|
498
|
+
feature_name = parts[0]
|
|
499
|
+
command = BeginFeatureCommand(feature_name=feature_name)
|
|
500
|
+
|
|
501
|
+
# Look for R min max
|
|
502
|
+
idx = 1
|
|
503
|
+
while idx < len(parts):
|
|
504
|
+
if parts[idx] == "R" and idx + 2 < len(parts):
|
|
505
|
+
idx += 1
|
|
506
|
+
try:
|
|
507
|
+
command.min_value = Decimal(parts[idx])
|
|
508
|
+
idx += 1
|
|
509
|
+
command.max_value = Decimal(parts[idx])
|
|
510
|
+
idx += 1
|
|
511
|
+
except Exception: # noqa: BLE001
|
|
512
|
+
self._add_error("invalid feature range", rest, line_num)
|
|
513
|
+
else:
|
|
514
|
+
idx += 1
|
|
515
|
+
|
|
516
|
+
return command
|
|
517
|
+
|
|
518
|
+
def _parse_bounds(
|
|
519
|
+
self,
|
|
520
|
+
parts: list[str],
|
|
521
|
+
start_idx: int,
|
|
522
|
+
line_num: int,
|
|
523
|
+
) -> tuple[Bounds, int]:
|
|
524
|
+
"""Parse bounds (minN maxN minE maxE minV maxV).
|
|
525
|
+
|
|
526
|
+
Returns (Bounds, new_index) tuple.
|
|
527
|
+
"""
|
|
528
|
+
bounds = Bounds()
|
|
529
|
+
idx = start_idx
|
|
530
|
+
|
|
531
|
+
if idx + 5 < len(parts):
|
|
532
|
+
min_n = self._parse_number(parts[idx], line_num, "min northing")
|
|
533
|
+
idx += 1
|
|
534
|
+
max_n = self._parse_number(parts[idx], line_num, "max northing")
|
|
535
|
+
idx += 1
|
|
536
|
+
min_e = self._parse_number(parts[idx], line_num, "min easting")
|
|
537
|
+
idx += 1
|
|
538
|
+
max_e = self._parse_number(parts[idx], line_num, "max easting")
|
|
539
|
+
idx += 1
|
|
540
|
+
min_v = self._parse_number(parts[idx], line_num, "min vertical")
|
|
541
|
+
idx += 1
|
|
542
|
+
max_v = self._parse_number(parts[idx], line_num, "max vertical")
|
|
543
|
+
idx += 1
|
|
544
|
+
|
|
545
|
+
bounds.lower = Location(northing=min_n, easting=min_e, vertical=min_v)
|
|
546
|
+
bounds.upper = Location(northing=max_n, easting=max_e, vertical=max_v)
|
|
547
|
+
|
|
548
|
+
return bounds, idx
|
|
549
|
+
|
|
550
|
+
def _parse_survey_bounds(
|
|
551
|
+
self,
|
|
552
|
+
rest: str,
|
|
553
|
+
line_num: int,
|
|
554
|
+
) -> SurveyBoundsCommand:
|
|
555
|
+
"""Parse X (survey bounds) command."""
|
|
556
|
+
parts = rest.split()
|
|
557
|
+
bounds, _ = self._parse_bounds(parts, 0, line_num)
|
|
558
|
+
return SurveyBoundsCommand(bounds=bounds)
|
|
559
|
+
|
|
560
|
+
def _parse_cave_bounds(
|
|
561
|
+
self,
|
|
562
|
+
rest: str,
|
|
563
|
+
line_num: int,
|
|
564
|
+
) -> CaveBoundsCommand:
|
|
565
|
+
"""Parse Z (cave bounds) command."""
|
|
566
|
+
parts = rest.split()
|
|
567
|
+
bounds, idx = self._parse_bounds(parts, 0, line_num)
|
|
568
|
+
|
|
569
|
+
command = CaveBoundsCommand(bounds=bounds)
|
|
570
|
+
|
|
571
|
+
# Look for I (distance to farthest station)
|
|
572
|
+
while idx < len(parts):
|
|
573
|
+
if parts[idx] == "I" and idx + 1 < len(parts):
|
|
574
|
+
idx += 1
|
|
575
|
+
dist = self._parse_number(
|
|
576
|
+
parts[idx],
|
|
577
|
+
line_num,
|
|
578
|
+
"distance to farthest station",
|
|
579
|
+
)
|
|
580
|
+
if dist is not None:
|
|
581
|
+
if dist < 0:
|
|
582
|
+
self._add_warning(
|
|
583
|
+
"distance to farthest station is negative",
|
|
584
|
+
parts[idx],
|
|
585
|
+
line_num,
|
|
586
|
+
)
|
|
587
|
+
command.distance_to_farthest_station = dist
|
|
588
|
+
idx += 1
|
|
589
|
+
else:
|
|
590
|
+
idx += 1
|
|
591
|
+
|
|
592
|
+
return command
|
|
593
|
+
|
|
594
|
+
def _parse_datum(
|
|
595
|
+
self,
|
|
596
|
+
rest: str,
|
|
597
|
+
line_num: int,
|
|
598
|
+
) -> DatumCommand:
|
|
599
|
+
"""Parse O (datum) command."""
|
|
600
|
+
datum = rest.split(maxsplit=1)[0] if rest.split() else rest.strip()
|
|
601
|
+
return DatumCommand(datum=datum)
|
|
602
|
+
|
|
603
|
+
def _parse_utm_zone(
|
|
604
|
+
self,
|
|
605
|
+
rest: str,
|
|
606
|
+
line_num: int,
|
|
607
|
+
) -> UtmZoneCommand:
|
|
608
|
+
"""Parse G (UTM zone) command."""
|
|
609
|
+
utm_zone = rest.split(maxsplit=1)[0] if rest.split() else rest.strip()
|
|
610
|
+
return UtmZoneCommand(utm_zone=utm_zone)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Project module for parsing and formatting Compass .MAK files."""
|
|
3
|
+
|
|
4
|
+
from compass_lib.project.format import format_directive
|
|
5
|
+
from compass_lib.project.format import format_mak_file
|
|
6
|
+
from compass_lib.project.format import format_project
|
|
7
|
+
from compass_lib.project.models import CommentDirective
|
|
8
|
+
from compass_lib.project.models import CompassMakFile
|
|
9
|
+
from compass_lib.project.models import CompassProjectDirective
|
|
10
|
+
from compass_lib.project.models import DatumDirective
|
|
11
|
+
from compass_lib.project.models import FileDirective
|
|
12
|
+
from compass_lib.project.models import FlagsDirective
|
|
13
|
+
from compass_lib.project.models import LinkStation
|
|
14
|
+
from compass_lib.project.models import LocationDirective
|
|
15
|
+
from compass_lib.project.models import UnknownDirective
|
|
16
|
+
from compass_lib.project.models import UTMConvergenceDirective
|
|
17
|
+
from compass_lib.project.models import UTMZoneDirective
|
|
18
|
+
from compass_lib.project.parser import CompassProjectParser
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"CommentDirective",
|
|
22
|
+
"CompassMakFile",
|
|
23
|
+
"CompassProjectDirective",
|
|
24
|
+
"CompassProjectParser",
|
|
25
|
+
"DatumDirective",
|
|
26
|
+
"FileDirective",
|
|
27
|
+
"FlagsDirective",
|
|
28
|
+
"LinkStation",
|
|
29
|
+
"LocationDirective",
|
|
30
|
+
"UTMConvergenceDirective",
|
|
31
|
+
"UTMZoneDirective",
|
|
32
|
+
"UnknownDirective",
|
|
33
|
+
"format_directive",
|
|
34
|
+
"format_mak_file",
|
|
35
|
+
"format_project",
|
|
36
|
+
]
|