compass-lib 0.0.3__tar.gz → 0.0.4__tar.gz

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.
Files changed (40) hide show
  1. {compass_lib-0.0.3 → compass_lib-0.0.4}/PKG-INFO +5 -2
  2. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/__init__.py +8 -8
  3. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/commands/convert.py +8 -6
  4. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/commands/geojson.py +43 -0
  5. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/constants.py +14 -0
  6. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/enums.py +53 -15
  7. compass_lib-0.0.4/compass_lib/geojson.py +2141 -0
  8. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/interface.py +4 -4
  9. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/io.py +7 -7
  10. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/project/models.py +6 -4
  11. compass_lib-0.0.4/compass_lib/solver/__init__.py +43 -0
  12. compass_lib-0.0.4/compass_lib/solver/ariane.py +488 -0
  13. compass_lib-0.0.4/compass_lib/solver/base.py +54 -0
  14. compass_lib-0.0.4/compass_lib/solver/lse.py +304 -0
  15. compass_lib-0.0.4/compass_lib/solver/models.py +229 -0
  16. compass_lib-0.0.4/compass_lib/solver/noop.py +28 -0
  17. compass_lib-0.0.4/compass_lib/solver/proportional.py +201 -0
  18. compass_lib-0.0.4/compass_lib/solver/sparse.py +249 -0
  19. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/survey/__init__.py +8 -8
  20. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/survey/format.py +58 -51
  21. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/survey/models.py +33 -67
  22. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/survey/parser.py +94 -235
  23. compass_lib-0.0.4/compass_lib/validation.py +241 -0
  24. {compass_lib-0.0.3 → compass_lib-0.0.4}/pyproject.toml +4 -1
  25. compass_lib-0.0.3/compass_lib/geojson.py +0 -1024
  26. compass_lib-0.0.3/compass_lib/validation.py +0 -74
  27. {compass_lib-0.0.3 → compass_lib-0.0.4}/LICENSE +0 -0
  28. {compass_lib-0.0.3 → compass_lib-0.0.4}/README.md +0 -0
  29. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/commands/__init__.py +0 -0
  30. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/commands/encrypt.py +0 -0
  31. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/commands/main.py +0 -0
  32. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/errors.py +0 -0
  33. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/geo_utils.py +0 -0
  34. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/models.py +0 -0
  35. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/plot/__init__.py +0 -0
  36. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/plot/models.py +0 -0
  37. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/plot/parser.py +0 -0
  38. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/project/__init__.py +0 -0
  39. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/project/format.py +0 -0
  40. {compass_lib-0.0.3 → compass_lib-0.0.4}/compass_lib/project/parser.py +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: compass_lib
3
- Version: 0.0.3
3
+ Version: 0.0.4
4
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>
8
- Requires-Python: >=3.10
8
+ Requires-Python: >=3.11
9
9
  Description-Content-Type: text/markdown
10
10
  Classifier: Development Status :: 3 - Alpha
11
11
  Classifier: Intended Audience :: Developers
@@ -21,11 +21,14 @@ Classifier: Programming Language :: Python :: 3
21
21
  Classifier: Programming Language :: Python :: 3 :: Only
22
22
  License-File: LICENSE
23
23
  Requires-Dist: geojson>=3.2,<4
24
+ Requires-Dist: numpy>=1.26,<3
25
+ Requires-Dist: scipy>=1.12,<2
24
26
  Requires-Dist: orjson>=3.10,<3.12
25
27
  Requires-Dist: pydantic>=2.12,<2.13
26
28
  Requires-Dist: pydantic-extra-types>=2.11,<3.0
27
29
  Requires-Dist: pyIGRF14==1.0.4
28
30
  Requires-Dist: pyproj>=3.7.1,<3.8
31
+ Requires-Dist: shapely>=2.0,<3
29
32
  Requires-Dist: utm>=0.8.1,<0.9
30
33
  Requires-Dist: cryptography>=44.0.0,<47.0.0 ; extra == "test"
31
34
  Requires-Dist: python-dotenv>=1.0.0,<2.0.0 ; extra == "test"
@@ -12,16 +12,16 @@ Usage:
12
12
  for file_dir in project.file_directives:
13
13
  print(f"File: {file_dir.file}")
14
14
  if file_dir.data:
15
- for trip in file_dir.data.trips:
16
- print(f" Survey: {trip.header.survey_name}")
15
+ for survey in file_dir.data.surveys:
16
+ print(f" Survey: {survey.header.survey_name}")
17
17
 
18
18
  # Or load individual files
19
19
  from compass_lib import read_dat_file, read_mak_file
20
- trips = read_dat_file(Path("survey.DAT"))
20
+ surveys = read_dat_file(Path("survey.DAT"))
21
21
  directives = read_mak_file(Path("project.MAK"))
22
22
  """
23
23
 
24
- __version__ = "0.0.3"
24
+ __version__ = "0.0.4"
25
25
 
26
26
  # Constants
27
27
  from compass_lib.constants import COMPASS_ENCODING
@@ -60,8 +60,8 @@ from compass_lib.project.models import FileDirective
60
60
  from compass_lib.project.models import LinkStation
61
61
  from compass_lib.survey.models import CompassDatFile
62
62
  from compass_lib.survey.models import CompassShot
63
- from compass_lib.survey.models import CompassTrip
64
- from compass_lib.survey.models import CompassTripHeader
63
+ from compass_lib.survey.models import CompassSurvey
64
+ from compass_lib.survey.models import CompassSurveyHeader
65
65
  from compass_lib.validation import days_in_month
66
66
  from compass_lib.validation import is_valid_station_name
67
67
  from compass_lib.validation import validate_station_name
@@ -88,8 +88,8 @@ __all__ = [
88
88
  "CompassParseError",
89
89
  "CompassParseException",
90
90
  "CompassShot",
91
- "CompassTrip",
92
- "CompassTripHeader",
91
+ "CompassSurvey",
92
+ "CompassSurveyHeader",
93
93
  "DrawOperation",
94
94
  "FileDirective",
95
95
  "FileFormat",
@@ -120,13 +120,15 @@ def _convert(
120
120
  if source_format == FileFormat.COMPASS and target_format == FileFormat.JSON:
121
121
  # Compass -> JSON using Pydantic
122
122
  if file_type == CompassFileType.DAT:
123
- trips = read_dat_file(input_path)
124
- dat_file = CompassDatFile(trips=trips)
123
+ surveys = read_dat_file(input_path)
124
+ dat_file = CompassDatFile(surveys=surveys)
125
125
  # Wrap in format envelope for DAT files
126
126
  envelope = {
127
127
  "version": "1.0",
128
128
  "format": FormatIdentifier.COMPASS_DAT.value,
129
- "trips": json.loads(dat_file.model_dump_json(by_alias=True))["trips"],
129
+ "surveys": json.loads(dat_file.model_dump_json(by_alias=True))[
130
+ "surveys"
131
+ ],
130
132
  }
131
133
  result = json.dumps(envelope, indent=2, sort_keys=True)
132
134
  else: # mak
@@ -139,9 +141,9 @@ def _convert(
139
141
  if file_type == CompassFileType.DAT:
140
142
  data = json.loads(json_str)
141
143
  # Handle both envelope format and raw format
142
- trips_data = data.get("trips", [])
143
- dat_file = CompassDatFile.model_validate({"trips": trips_data})
144
- result = format_dat_file(dat_file.trips) or ""
144
+ surveys_data = data.get("surveys", [])
145
+ dat_file = CompassDatFile.model_validate({"surveys": surveys_data})
146
+ result = format_dat_file(dat_file.surveys) or ""
145
147
  else: # mak
146
148
  project = CompassMakFile.model_validate_json(json_str)
147
149
  result = format_mak_file(project.directives) or ""
@@ -11,6 +11,10 @@ from pathlib import Path
11
11
 
12
12
  from compass_lib.enums import FileExtension
13
13
  from compass_lib.geojson import convert_mak_to_geojson
14
+ from compass_lib.solver.ariane import ArianeSolver
15
+ from compass_lib.solver.lse import LSESolver
16
+ from compass_lib.solver.proportional import ProportionalSolver
17
+ from compass_lib.solver.sparse import SparseSolver
14
18
 
15
19
  logger = logging.getLogger(__name__)
16
20
 
@@ -74,6 +78,32 @@ Notes:
74
78
  action="store_true",
75
79
  help="Include passage polygon features (from LRUD data)",
76
80
  )
81
+ parser.add_argument(
82
+ "--anchors",
83
+ action="store_true",
84
+ help="Include anchor point features (fixed reference stations)",
85
+ )
86
+ parser.add_argument(
87
+ "--no-colors",
88
+ action="store_true",
89
+ help="Disable origin-based coloring of stations and legs",
90
+ )
91
+ parser.add_argument(
92
+ "--solver",
93
+ choices=["proportional", "sparse", "lse", "ariane", "none"],
94
+ default="proportional",
95
+ help="Survey adjustment solver: "
96
+ "proportional (L2, spreads error evenly), "
97
+ "sparse (L1, fewest/smallest changes), "
98
+ "lse (blunder-aware, protects good data), "
99
+ "ariane (sparse CG + blunder-aware, scales to large caves), "
100
+ "none (raw BFS, shows misclosure gaps)",
101
+ )
102
+ parser.add_argument(
103
+ "--minify",
104
+ action="store_true",
105
+ help="Compact output without indentation (smaller file size)",
106
+ )
77
107
 
78
108
  parsed_args = parser.parse_args(args)
79
109
 
@@ -89,12 +119,25 @@ Notes:
89
119
  return 1
90
120
 
91
121
  try:
122
+ solvers = {
123
+ "proportional": ProportionalSolver,
124
+ "sparse": SparseSolver,
125
+ "lse": LSESolver,
126
+ "ariane": ArianeSolver,
127
+ "none": lambda: None,
128
+ }
129
+ solver = solvers[parsed_args.solver]()
130
+
92
131
  result = convert_mak_to_geojson(
93
132
  parsed_args.input_file,
94
133
  output_path=parsed_args.output_file,
95
134
  include_stations=not parsed_args.no_stations,
96
135
  include_legs=not parsed_args.no_legs,
97
136
  include_passages=parsed_args.passages,
137
+ include_anchors=parsed_args.anchors,
138
+ color_by_origin=not parsed_args.no_colors,
139
+ solver=solver,
140
+ minify=parsed_args.minify,
98
141
  )
99
142
 
100
143
  if parsed_args.output_file is None:
@@ -82,3 +82,17 @@ FLAG_CHARS: dict[str, str] = {
82
82
  #: (positive = north, negative = south), NOT by comparing northing to this threshold.
83
83
  #: This constant is kept for reference and potential future use.
84
84
  UTM_SOUTHERN_HEMISPHERE_OFFSET: float = 10_000_000.0
85
+
86
+ # -----------------------------------------------------------------------------
87
+ # Solver Constraints
88
+ # -----------------------------------------------------------------------------
89
+
90
+ #: Maximum relative change to a shot's tape length during traverse adjustment
91
+ #: (0.05 = 5 %). The solver will never stretch or shrink a shot by more than
92
+ #: this fraction of the original survey measurement.
93
+ SOLVER_MAX_LENGTH_CHANGE: float = 0.15
94
+
95
+ #: Maximum relative change to a shot's compass heading during traverse
96
+ #: adjustment (0.15 = 15 %). The solver will never rotate a shot's bearing
97
+ #: or inclination by more than this fraction of the original survey reading.
98
+ SOLVER_MAX_HEADING_CHANGE: float = 0.15
@@ -5,14 +5,14 @@ This module contains all enumerations used in Compass survey data formats,
5
5
  including units for measurements, file types, and various classification types.
6
6
  """
7
7
 
8
- from enum import Enum
8
+ from enum import StrEnum
9
9
  from math import radians
10
10
  from math import tan
11
11
 
12
12
  from compass_lib.constants import FEET_TO_METERS
13
13
 
14
14
 
15
- class FileFormat(str, Enum):
15
+ class FileFormat(StrEnum):
16
16
  """File format types for conversion operations.
17
17
 
18
18
  Attributes:
@@ -26,7 +26,7 @@ class FileFormat(str, Enum):
26
26
  GEOJSON = "geojson"
27
27
 
28
28
 
29
- class CompassFileType(str, Enum):
29
+ class CompassFileType(StrEnum):
30
30
  """Types of Compass files.
31
31
 
32
32
  Attributes:
@@ -67,7 +67,7 @@ class CompassFileType(str, Enum):
67
67
  return mapping.get(ext_lower)
68
68
 
69
69
 
70
- class FileExtension(str, Enum):
70
+ class FileExtension(StrEnum):
71
71
  """File extensions for various file formats (with dot).
72
72
 
73
73
  Attributes:
@@ -85,7 +85,7 @@ class FileExtension(str, Enum):
85
85
  GEOJSON = ".geojson"
86
86
 
87
87
 
88
- class FormatIdentifier(str, Enum):
88
+ class FormatIdentifier(StrEnum):
89
89
  """Format identifiers used in JSON files.
90
90
 
91
91
  Attributes:
@@ -97,7 +97,7 @@ class FormatIdentifier(str, Enum):
97
97
  COMPASS_MAK = "compass_mak"
98
98
 
99
99
 
100
- class AzimuthUnit(str, Enum):
100
+ class AzimuthUnit(StrEnum):
101
101
  """Unit for compass azimuth (bearing) measurements.
102
102
 
103
103
  Attributes:
@@ -128,7 +128,7 @@ class AzimuthUnit(str, Enum):
128
128
  return degrees
129
129
 
130
130
 
131
- class InclinationUnit(str, Enum):
131
+ class InclinationUnit(StrEnum):
132
132
  """Unit for vertical angle (inclination) measurements.
133
133
 
134
134
  Attributes:
@@ -136,7 +136,9 @@ class InclinationUnit(str, Enum):
136
136
  PERCENT_GRADE: Percentage gradient (tan(angle) * 100)
137
137
  DEGREES_AND_MINUTES: Degrees with minutes notation
138
138
  GRADS: Gradians
139
- DEPTH_GAUGE: Depth gauge reading
139
+ DEPTH_GAUGE: Delta depth (From_depth - To_depth) for underwater surveys.
140
+ Negative = descending (going deeper), positive = ascending.
141
+ Uses the same units as length measurements.
140
142
  """
141
143
 
142
144
  DEGREES = "D"
@@ -165,7 +167,7 @@ class InclinationUnit(str, Enum):
165
167
  return value
166
168
 
167
169
 
168
- class LengthUnit(str, Enum):
170
+ class LengthUnit(StrEnum):
169
171
  """Unit for distance measurements.
170
172
 
171
173
  Attributes:
@@ -196,7 +198,7 @@ class LengthUnit(str, Enum):
196
198
  return feet
197
199
 
198
200
 
199
- class LrudAssociation(str, Enum):
201
+ class LrudAssociation(StrEnum):
200
202
  """Indicates which station LRUD measurements are associated with.
201
203
 
202
204
  Attributes:
@@ -208,7 +210,7 @@ class LrudAssociation(str, Enum):
208
210
  TO = "T"
209
211
 
210
212
 
211
- class LrudItem(str, Enum):
213
+ class LrudItem(StrEnum):
212
214
  """Individual LRUD dimension identifiers.
213
215
 
214
216
  Attributes:
@@ -224,7 +226,7 @@ class LrudItem(str, Enum):
224
226
  DOWN = "D"
225
227
 
226
228
 
227
- class ShotItem(str, Enum):
229
+ class ShotItem(StrEnum):
228
230
  """Components of a shot measurement for format ordering.
229
231
 
230
232
  Attributes:
@@ -242,7 +244,7 @@ class ShotItem(str, Enum):
242
244
  BACKSIGHT_INCLINATION = "d"
243
245
 
244
246
 
245
- class Severity(str, Enum):
247
+ class Severity(StrEnum):
246
248
  """Severity level for parse errors.
247
249
 
248
250
  Attributes:
@@ -254,7 +256,7 @@ class Severity(str, Enum):
254
256
  WARNING = "warning"
255
257
 
256
258
 
257
- class DrawOperation(str, Enum):
259
+ class DrawOperation(StrEnum):
258
260
  """Drawing operations for plot commands.
259
261
 
260
262
  Attributes:
@@ -266,7 +268,7 @@ class DrawOperation(str, Enum):
266
268
  LINE_TO = "D"
267
269
 
268
270
 
269
- class Datum(str, Enum):
271
+ class Datum(StrEnum):
270
272
  """Geodetic datum values supported by Compass.
271
273
 
272
274
  These are the standard datum values that can be used in MAK project files.
@@ -322,6 +324,42 @@ class Datum(str, Enum):
322
324
  WGS_1972 = "WGS 1972"
323
325
  WGS_1984 = "WGS 1984"
324
326
 
327
+ def get_utm_epsg(self, zone: int, northern: bool) -> str:
328
+ """Get the EPSG code for a UTM zone with this datum.
329
+
330
+ Args:
331
+ zone: UTM zone number (1-60)
332
+ northern: True for northern hemisphere
333
+
334
+ Returns:
335
+ EPSG code string (e.g., "EPSG:26717" for NAD27 UTM Zone 17N)
336
+ """
337
+ zone = abs(zone)
338
+
339
+ match self:
340
+ case Datum.NORTH_AMERICAN_1927:
341
+ # NAD27 / UTM: EPSG:26701-26722 (zones 1-22 north)
342
+ return f"EPSG:{26700 + zone}"
343
+
344
+ case Datum.NORTH_AMERICAN_1983:
345
+ # NAD83 / UTM: EPSG:26901-26923 (zones 1-23 north)
346
+ return f"EPSG:{26900 + zone}"
347
+
348
+ case Datum.WGS_1972:
349
+ # WGS72 / UTM: EPSG:32201-32260 (north), EPSG:32301-32360 (south)
350
+ if northern:
351
+ return f"EPSG:{32200 + zone}"
352
+ return f"EPSG:{32300 + zone}"
353
+
354
+ case Datum.WGS_1984:
355
+ # WGS84 / UTM: EPSG:32601-32660 (north), EPSG:32701-32760 (south)
356
+ if northern:
357
+ return f"EPSG:{32600 + zone}"
358
+ return f"EPSG:{32700 + zone}"
359
+
360
+ case _:
361
+ raise NotImplementedError(f"This DATUM: `{self}` is not supported")
362
+
325
363
  @classmethod
326
364
  def normalize(cls, value: str | None) -> "Datum | None":
327
365
  """Normalize and validate a datum string to a Datum enum value.