compass-lib 0.0.8__tar.gz → 0.0.9__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.
- compass_lib-0.0.9/PKG-INFO +107 -0
- compass_lib-0.0.9/README.md +59 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/__init__.py +1 -1
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/constants.py +19 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/geojson.py +17 -12
- compass_lib-0.0.9/compass_lib/section_colors.py +104 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/survey/models.py +2 -0
- compass_lib-0.0.8/PKG-INFO +0 -63
- compass_lib-0.0.8/README.md +0 -15
- {compass_lib-0.0.8 → compass_lib-0.0.9}/LICENSE +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/commands/__init__.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/commands/convert.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/commands/encrypt.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/commands/geojson.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/commands/main.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/enums.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/errors.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/geo_utils.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/interface.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/io.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/models.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/plot/__init__.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/plot/models.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/plot/parser.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/project/__init__.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/project/format.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/project/models.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/project/parser.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/solver/__init__.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/solver/ariane.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/solver/base.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/solver/lse.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/solver/models.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/solver/noop.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/solver/proportional.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/solver/sparse.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/survey/__init__.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/survey/format.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/survey/parser.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/compass_lib/validation.py +0 -0
- {compass_lib-0.0.8 → compass_lib-0.0.9}/pyproject.toml +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: compass_lib
|
|
3
|
+
Version: 0.0.9
|
|
4
|
+
Summary: Compass Parser Library.
|
|
5
|
+
Keywords: cave,survey,karst
|
|
6
|
+
Author-email: Jonathan Dekhtiar <jonathan@dekhtiar.com>
|
|
7
|
+
Maintainer-email: Jonathan Dekhtiar <jonathan@dekhtiar.com>
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Intended Audience :: Information Technology
|
|
14
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Classifier: Topic :: Utilities
|
|
18
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: geojson>=3.2,<4
|
|
24
|
+
Requires-Dist: numpy>=2.4.4,<3
|
|
25
|
+
Requires-Dist: scipy>=1.17.1,<2
|
|
26
|
+
Requires-Dist: orjson>=3.12,<4
|
|
27
|
+
Requires-Dist: pydantic>=2.13,<3
|
|
28
|
+
Requires-Dist: pydantic-extra-types>=2.11.1,<3
|
|
29
|
+
Requires-Dist: pyIGRF14>=1.0.4,<2
|
|
30
|
+
Requires-Dist: pyproj>=3.7.2,<4
|
|
31
|
+
Requires-Dist: shapely>=2.1.2,<3
|
|
32
|
+
Requires-Dist: utm>=0.8.1,<1
|
|
33
|
+
Requires-Dist: cryptography>=50.0.0,<51 ; extra == "test"
|
|
34
|
+
Requires-Dist: python-dotenv>=1.2.2,<2 ; extra == "test"
|
|
35
|
+
Requires-Dist: deepdiff>=7.0,<10 ; extra == "test"
|
|
36
|
+
Requires-Dist: pytest>=9.0.3,<10 ; extra == "test"
|
|
37
|
+
Requires-Dist: pytest-cov>=7.1.0,<8 ; extra == "test"
|
|
38
|
+
Requires-Dist: pytest-env>=1.6.0,<2 ; extra == "test"
|
|
39
|
+
Requires-Dist: pytest-runner>=6.0.1,<7 ; extra == "test"
|
|
40
|
+
Requires-Dist: pytest-ordering>=0.6,<1 ; extra == "test"
|
|
41
|
+
Requires-Dist: parameterized>=0.9.0,<1 ; extra == "test"
|
|
42
|
+
Project-URL: Bug Reports, https://github.com/OpenSpeleo/pytool_compass_lib/issues
|
|
43
|
+
Project-URL: Homepage, https://pypi.org/project/compass-lib/
|
|
44
|
+
Project-URL: Source, https://github.com/OpenSpeleo/pytool_compass_lib
|
|
45
|
+
Provides-Extra: test
|
|
46
|
+
Import-Name: compass_lib
|
|
47
|
+
|
|
48
|
+
# Compass Python Lib
|
|
49
|
+
|
|
50
|
+
## Conversion commands:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Install in dev mod
|
|
54
|
+
pip install -e ".[dev,test]"
|
|
55
|
+
|
|
56
|
+
# Install latest stable version
|
|
57
|
+
pip install compass_lib
|
|
58
|
+
|
|
59
|
+
# run some commands
|
|
60
|
+
compass convert --input_file=./tests/artifacts/fulford.dat --output_file=fulford.json --format=json --overwrite
|
|
61
|
+
compass convert --input_file=./tests/artifacts/random.dat --output_file=random.json --format=json --overwrite
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## GeoJSON section colors
|
|
65
|
+
|
|
66
|
+
GeoJSON survey legs include `properties.color`, an opaque CSS `#rrggbb` color.
|
|
67
|
+
Every shot in a DAT survey block receives the same generated section color.
|
|
68
|
+
Sections sharing a station have different colors, including across MAK-linked
|
|
69
|
+
DAT files. Names alone do not identify sections or station connections: the
|
|
70
|
+
existing MAK station-scoping rules remain authoritative. Geometric crossings
|
|
71
|
+
without a shared station do not create a color constraint.
|
|
72
|
+
|
|
73
|
+
The palette lives in `compass_lib/constants.py` as `SURVEY_COLORS`: vivid red,
|
|
74
|
+
orange, yellow, lime, green, mint, cyan, blue, purple, and hot pink. Both
|
|
75
|
+
palette colors and generated overflow colors have HSV saturation of at least 80%
|
|
76
|
+
and value of at least 90%, excluding black, white, gray, and muted shades.
|
|
77
|
+
|
|
78
|
+
Assignment uses ordinary random choices in at most 16 iterative passes, without
|
|
79
|
+
hashing, a fixed seed, or algorithm versioning. It stops on completion or no
|
|
80
|
+
improvement. Remaining sections receive distinct vibrant colors using a finite
|
|
81
|
+
fallback budget sized to the input, so reaching the pass limit does not leave
|
|
82
|
+
touching sections with the same viewer fallback color. Colors can change when
|
|
83
|
+
GeoJSON is regenerated and remain fixed in each stored artifact.
|
|
84
|
+
|
|
85
|
+
See [GeoJSON section coloring](docs/geojson-section-colors.md) for pass
|
|
86
|
+
semantics, fallback bounds, the color-space limit, export behavior, and test
|
|
87
|
+
coverage.
|
|
88
|
+
|
|
89
|
+
`CompassSurvey.color` is derived metadata assigned during coordinate
|
|
90
|
+
computation; it is excluded from source JSON and DAT/MAK serialization.
|
|
91
|
+
`SurveyLeg.color` carries it through coordinate propagation and adjustment.
|
|
92
|
+
Passage polygons and clipped passage fragments inherit their leg's color.
|
|
93
|
+
Station/anchor and misclosure styling retain their existing semantics.
|
|
94
|
+
|
|
95
|
+
The `color` property is independent of `color_by_origin` and CLI `--no-colors`,
|
|
96
|
+
which still control origin-based simplestyle `stroke`/`marker-color` styling.
|
|
97
|
+
Clients can use `color` for shot rendering and fall back to the survey color for
|
|
98
|
+
legacy data without it. Existing saved GeoJSON must be regenerated to gain these
|
|
99
|
+
properties; no source files are rewritten. Palette changes also require
|
|
100
|
+
regenerating stored GeoJSON; existing artifacts retain the colors recorded when
|
|
101
|
+
they were exported.
|
|
102
|
+
|
|
103
|
+
Portable regression coverage lives in `tests/test_section_colors.py`, including
|
|
104
|
+
duplicate names, linked/scoped stations, crowded junctions, solver paths, round
|
|
105
|
+
trips, CLI/API exports, vibrant colors, and safe budget exhaustion. Private
|
|
106
|
+
fixtures remain additional coverage and are never required for these guarantees.
|
|
107
|
+
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Compass Python Lib
|
|
2
|
+
|
|
3
|
+
## Conversion commands:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# Install in dev mod
|
|
7
|
+
pip install -e ".[dev,test]"
|
|
8
|
+
|
|
9
|
+
# Install latest stable version
|
|
10
|
+
pip install compass_lib
|
|
11
|
+
|
|
12
|
+
# run some commands
|
|
13
|
+
compass convert --input_file=./tests/artifacts/fulford.dat --output_file=fulford.json --format=json --overwrite
|
|
14
|
+
compass convert --input_file=./tests/artifacts/random.dat --output_file=random.json --format=json --overwrite
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## GeoJSON section colors
|
|
18
|
+
|
|
19
|
+
GeoJSON survey legs include `properties.color`, an opaque CSS `#rrggbb` color.
|
|
20
|
+
Every shot in a DAT survey block receives the same generated section color.
|
|
21
|
+
Sections sharing a station have different colors, including across MAK-linked
|
|
22
|
+
DAT files. Names alone do not identify sections or station connections: the
|
|
23
|
+
existing MAK station-scoping rules remain authoritative. Geometric crossings
|
|
24
|
+
without a shared station do not create a color constraint.
|
|
25
|
+
|
|
26
|
+
The palette lives in `compass_lib/constants.py` as `SURVEY_COLORS`: vivid red,
|
|
27
|
+
orange, yellow, lime, green, mint, cyan, blue, purple, and hot pink. Both
|
|
28
|
+
palette colors and generated overflow colors have HSV saturation of at least 80%
|
|
29
|
+
and value of at least 90%, excluding black, white, gray, and muted shades.
|
|
30
|
+
|
|
31
|
+
Assignment uses ordinary random choices in at most 16 iterative passes, without
|
|
32
|
+
hashing, a fixed seed, or algorithm versioning. It stops on completion or no
|
|
33
|
+
improvement. Remaining sections receive distinct vibrant colors using a finite
|
|
34
|
+
fallback budget sized to the input, so reaching the pass limit does not leave
|
|
35
|
+
touching sections with the same viewer fallback color. Colors can change when
|
|
36
|
+
GeoJSON is regenerated and remain fixed in each stored artifact.
|
|
37
|
+
|
|
38
|
+
See [GeoJSON section coloring](docs/geojson-section-colors.md) for pass
|
|
39
|
+
semantics, fallback bounds, the color-space limit, export behavior, and test
|
|
40
|
+
coverage.
|
|
41
|
+
|
|
42
|
+
`CompassSurvey.color` is derived metadata assigned during coordinate
|
|
43
|
+
computation; it is excluded from source JSON and DAT/MAK serialization.
|
|
44
|
+
`SurveyLeg.color` carries it through coordinate propagation and adjustment.
|
|
45
|
+
Passage polygons and clipped passage fragments inherit their leg's color.
|
|
46
|
+
Station/anchor and misclosure styling retain their existing semantics.
|
|
47
|
+
|
|
48
|
+
The `color` property is independent of `color_by_origin` and CLI `--no-colors`,
|
|
49
|
+
which still control origin-based simplestyle `stroke`/`marker-color` styling.
|
|
50
|
+
Clients can use `color` for shot rendering and fall back to the survey color for
|
|
51
|
+
legacy data without it. Existing saved GeoJSON must be regenerated to gain these
|
|
52
|
+
properties; no source files are rewritten. Palette changes also require
|
|
53
|
+
regenerating stored GeoJSON; existing artifacts retain the colors recorded when
|
|
54
|
+
they were exported.
|
|
55
|
+
|
|
56
|
+
Portable regression coverage lives in `tests/test_section_colors.py`, including
|
|
57
|
+
duplicate names, linked/scoped stations, crowded junctions, solver paths, round
|
|
58
|
+
trips, CLI/API exports, vibrant colors, and safe budget exhaustion. Private
|
|
59
|
+
fixtures remain additional coverage and are never required for these guarantees.
|
|
@@ -15,6 +15,25 @@ COMPASS_ENCODING = "cp1252"
|
|
|
15
15
|
#: Encoding used for JSON files
|
|
16
16
|
JSON_ENCODING = "utf-8"
|
|
17
17
|
|
|
18
|
+
#: Bright, saturated palette shared by section colors and origin debug styling.
|
|
19
|
+
#: No black, white, gray, brown, or muted shades.
|
|
20
|
+
SURVEY_COLORS: tuple[str, ...] = (
|
|
21
|
+
"#ff1744", # vivid red
|
|
22
|
+
"#ff6d00", # bright orange
|
|
23
|
+
"#ffea00", # electric yellow
|
|
24
|
+
"#aaff00", # lime
|
|
25
|
+
"#00e676", # spring green
|
|
26
|
+
"#00ffb3", # bright mint
|
|
27
|
+
"#00e5ff", # cyan
|
|
28
|
+
"#2979ff", # electric blue
|
|
29
|
+
"#d500f9", # purple
|
|
30
|
+
"#ff00a8", # hot pink
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
#: HSV bounds also enforced when crowded junctions require additional colors.
|
|
34
|
+
SURVEY_COLOR_MIN_SATURATION: float = 0.8
|
|
35
|
+
SURVEY_COLOR_MIN_VALUE: float = 0.9
|
|
36
|
+
|
|
18
37
|
#: Encoding used when reading raw Compass files (ASCII with replacements)
|
|
19
38
|
ASCII_ENCODING = "ascii"
|
|
20
39
|
|
|
@@ -55,10 +55,12 @@ from compass_lib.constants import FEET_TO_METERS
|
|
|
55
55
|
from compass_lib.constants import GEOJSON_COORDINATE_PRECISION
|
|
56
56
|
from compass_lib.constants import JSON_ENCODING
|
|
57
57
|
from compass_lib.constants import METERS_TO_FEET
|
|
58
|
+
from compass_lib.constants import SURVEY_COLORS
|
|
58
59
|
from compass_lib.enums import Datum
|
|
59
60
|
from compass_lib.geo_utils import GeoLocation
|
|
60
61
|
from compass_lib.geo_utils import get_declination
|
|
61
62
|
from compass_lib.io import load_project
|
|
63
|
+
from compass_lib.section_colors import assign_section_colors
|
|
62
64
|
from compass_lib.solver.models import SurveyNetwork
|
|
63
65
|
|
|
64
66
|
if TYPE_CHECKING:
|
|
@@ -139,6 +141,9 @@ class SurveyLeg:
|
|
|
139
141
|
but measurement deltas do not).
|
|
140
142
|
"""
|
|
141
143
|
|
|
144
|
+
color: str | None = None
|
|
145
|
+
"""Derived source-section color, independent of propagation origin."""
|
|
146
|
+
|
|
142
147
|
|
|
143
148
|
@dataclass
|
|
144
149
|
class MisclosureLeg:
|
|
@@ -185,18 +190,7 @@ class ComputedSurvey:
|
|
|
185
190
|
#: stations can be visually distinguished by their propagation source.
|
|
186
191
|
#: Viewers that support the simplestyle spec (geojson.io, GitHub, QGIS,
|
|
187
192
|
#: Mapbox, …) will render these automatically.
|
|
188
|
-
ORIGIN_COLORS: list[str] =
|
|
189
|
-
"#1f77b4", # blue
|
|
190
|
-
"#ff7f0e", # orange
|
|
191
|
-
"#2ca02c", # green
|
|
192
|
-
"#d62728", # red
|
|
193
|
-
"#9467bd", # purple
|
|
194
|
-
"#8c564b", # brown
|
|
195
|
-
"#e377c2", # pink
|
|
196
|
-
"#7f7f7f", # grey
|
|
197
|
-
"#bcbd22", # olive
|
|
198
|
-
"#17becf", # cyan
|
|
199
|
-
]
|
|
193
|
+
ORIGIN_COLORS: list[str] = list(SURVEY_COLORS)
|
|
200
194
|
|
|
201
195
|
|
|
202
196
|
# -----------------------------------------------------------------------------
|
|
@@ -757,6 +751,9 @@ def propagate_coordinates(
|
|
|
757
751
|
Returns:
|
|
758
752
|
ComputedSurvey with all computed stations and legs
|
|
759
753
|
"""
|
|
754
|
+
assign_section_colors(
|
|
755
|
+
project, ([edge[3] for edge in edges] for edges in adjacency.values())
|
|
756
|
+
)
|
|
760
757
|
result = ComputedSurvey()
|
|
761
758
|
|
|
762
759
|
# Set metadata
|
|
@@ -957,6 +954,7 @@ def propagate_coordinates(
|
|
|
957
954
|
inclination=shot.frontsight_inclination,
|
|
958
955
|
file=file_name,
|
|
959
956
|
survey=survey_name,
|
|
957
|
+
color=survey.color,
|
|
960
958
|
left=shot.left,
|
|
961
959
|
right=shot.right,
|
|
962
960
|
up=shot.up,
|
|
@@ -997,6 +995,7 @@ def propagate_coordinates(
|
|
|
997
995
|
inclination=shot.frontsight_inclination,
|
|
998
996
|
file=file_name,
|
|
999
997
|
survey=survey_name,
|
|
998
|
+
color=survey.color,
|
|
1000
999
|
left=shot.left,
|
|
1001
1000
|
right=shot.right,
|
|
1002
1001
|
up=shot.up,
|
|
@@ -1027,6 +1026,7 @@ def propagate_coordinates(
|
|
|
1027
1026
|
inclination=shot.frontsight_inclination,
|
|
1028
1027
|
file=file_name,
|
|
1029
1028
|
survey=survey_name,
|
|
1029
|
+
color=survey.color,
|
|
1030
1030
|
left=shot.left,
|
|
1031
1031
|
right=shot.right,
|
|
1032
1032
|
up=shot.up,
|
|
@@ -1240,6 +1240,7 @@ def propagate_coordinates(
|
|
|
1240
1240
|
inclination=shot.frontsight_inclination,
|
|
1241
1241
|
file=file_name,
|
|
1242
1242
|
survey=survey_name,
|
|
1243
|
+
color=survey.color,
|
|
1243
1244
|
left=shot.left,
|
|
1244
1245
|
right=shot.right,
|
|
1245
1246
|
up=shot.up,
|
|
@@ -1259,6 +1260,7 @@ def propagate_coordinates(
|
|
|
1259
1260
|
inclination=shot.frontsight_inclination,
|
|
1260
1261
|
file=file_name,
|
|
1261
1262
|
survey=survey_name,
|
|
1263
|
+
color=survey.color,
|
|
1262
1264
|
left=shot.left,
|
|
1263
1265
|
right=shot.right,
|
|
1264
1266
|
up=shot.up,
|
|
@@ -1415,6 +1417,7 @@ def propagate_coordinates(
|
|
|
1415
1417
|
inclination=shot.frontsight_inclination,
|
|
1416
1418
|
file=file_name,
|
|
1417
1419
|
survey=survey_name,
|
|
1420
|
+
color=survey.color,
|
|
1418
1421
|
left=shot.left,
|
|
1419
1422
|
right=shot.right,
|
|
1420
1423
|
up=shot.up,
|
|
@@ -1676,6 +1679,7 @@ def leg_to_feature(
|
|
|
1676
1679
|
"id": str(uuid.uuid5(uuid.NAMESPACE_OID, leg.to_station.name)),
|
|
1677
1680
|
"depth": abs(depth_ft), # Compass depth is < 0
|
|
1678
1681
|
"name": leg.survey,
|
|
1682
|
+
**({"color": leg.color} if leg.color is not None else {}),
|
|
1679
1683
|
},
|
|
1680
1684
|
)
|
|
1681
1685
|
|
|
@@ -1886,6 +1890,7 @@ def passage_to_feature(
|
|
|
1886
1890
|
),
|
|
1887
1891
|
properties={
|
|
1888
1892
|
"type": "passage",
|
|
1893
|
+
**({"color": leg.color} if leg.color is not None else {}),
|
|
1889
1894
|
"from": leg.from_station.name,
|
|
1890
1895
|
"to": leg.to_station.name,
|
|
1891
1896
|
"left_ft": leg.left,
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Bounded coloring passes respecting the computed station topology."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import random
|
|
7
|
+
from itertools import islice
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from compass_lib.constants import SURVEY_COLOR_MIN_SATURATION
|
|
11
|
+
from compass_lib.constants import SURVEY_COLOR_MIN_VALUE
|
|
12
|
+
from compass_lib.constants import SURVEY_COLORS
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Iterable
|
|
16
|
+
from collections.abc import Iterator
|
|
17
|
+
|
|
18
|
+
from compass_lib.project.models import CompassMakFile
|
|
19
|
+
from compass_lib.survey.models import CompassSurvey
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
MAX_COLORING_PASSES = 16
|
|
24
|
+
RGB_COLOR_COUNT = 1 << 24
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _additional_colors() -> Iterator[str]:
|
|
28
|
+
"""Visit distinct RGB values, accepting only bright, saturated colors."""
|
|
29
|
+
for index in range(RGB_COLOR_COUNT):
|
|
30
|
+
# The odd multiplier visits every RGB value once without a growing set.
|
|
31
|
+
rgb = (0x68BDEB + index * 0x9E3779) % RGB_COLOR_COUNT
|
|
32
|
+
channels = ((rgb >> 16) & 255, (rgb >> 8) & 255, rgb & 255)
|
|
33
|
+
peak = max(channels)
|
|
34
|
+
if peak / 255 < SURVEY_COLOR_MIN_VALUE:
|
|
35
|
+
continue
|
|
36
|
+
saturation = (peak - min(channels)) / peak
|
|
37
|
+
if saturation >= SURVEY_COLOR_MIN_SATURATION:
|
|
38
|
+
yield f"#{rgb:06x}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def assign_section_colors(
|
|
42
|
+
project: CompassMakFile,
|
|
43
|
+
station_sections: Iterable[Iterable[CompassSurvey]],
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Try random palette assignments in bounded passes, then use vivid extras.
|
|
46
|
+
|
|
47
|
+
Each DAT survey block is a separate section, even when names repeat. The
|
|
48
|
+
existing station graph supplies connectivity, including MAK station scopes.
|
|
49
|
+
Only conflict-free candidates coloring more sections improve the assignment;
|
|
50
|
+
no-progress and pass-budget exhaustion lead to a bounded fallback.
|
|
51
|
+
"""
|
|
52
|
+
sections = [
|
|
53
|
+
section
|
|
54
|
+
for directive in project.file_directives
|
|
55
|
+
if directive.data is not None
|
|
56
|
+
for section in directive.data.surveys
|
|
57
|
+
]
|
|
58
|
+
section_indices = {id(section): index for index, section in enumerate(sections)}
|
|
59
|
+
neighbors: list[set[int]] = [set() for _ in sections]
|
|
60
|
+
for owners in station_sections:
|
|
61
|
+
indices = {section_indices[id(section)] for section in owners}
|
|
62
|
+
for index in indices:
|
|
63
|
+
neighbors[index].update(indices - {index})
|
|
64
|
+
|
|
65
|
+
order = sorted(range(len(sections)), key=lambda index: -len(neighbors[index]))
|
|
66
|
+
colors: dict[int, str] = {}
|
|
67
|
+
for _ in range(MAX_COLORING_PASSES):
|
|
68
|
+
candidate: dict[int, str] = {}
|
|
69
|
+
for index in order:
|
|
70
|
+
forbidden = {
|
|
71
|
+
candidate[other] for other in neighbors[index] if other in candidate
|
|
72
|
+
}
|
|
73
|
+
available = [color for color in SURVEY_COLORS if color not in forbidden]
|
|
74
|
+
if available:
|
|
75
|
+
candidate[index] = random.choice(available)
|
|
76
|
+
if len(candidate) <= len(colors):
|
|
77
|
+
break
|
|
78
|
+
colors = candidate
|
|
79
|
+
if len(colors) == len(sections):
|
|
80
|
+
break
|
|
81
|
+
|
|
82
|
+
# Finish unresolved sections with distinct bright colors. The generator is
|
|
83
|
+
# unique, so at most len(used) candidates can collide with assigned colors.
|
|
84
|
+
# This input-sized bound completes the fallback without an arbitrary cap
|
|
85
|
+
# leaving touching sections to render the same survey fallback color.
|
|
86
|
+
used = set(colors.values())
|
|
87
|
+
candidate_budget = len(sections) - len(colors) + len(used)
|
|
88
|
+
extras = iter(islice(_additional_colors(), candidate_budget))
|
|
89
|
+
for index in order:
|
|
90
|
+
if index in colors:
|
|
91
|
+
continue
|
|
92
|
+
color = next((value for value in extras if value not in used), None)
|
|
93
|
+
if color is None:
|
|
94
|
+
logger.warning(
|
|
95
|
+
"Vibrant RGB color space exhausted; %s sections use viewer fallback",
|
|
96
|
+
len(sections) - len(colors),
|
|
97
|
+
)
|
|
98
|
+
break
|
|
99
|
+
colors[index] = color
|
|
100
|
+
used.add(color)
|
|
101
|
+
|
|
102
|
+
for index, section in enumerate(sections):
|
|
103
|
+
# Omit unassigned colors instead of exporting a conflicting color.
|
|
104
|
+
section.color = colors.get(index)
|
|
@@ -100,6 +100,8 @@ class CompassSurvey(BaseModel):
|
|
|
100
100
|
|
|
101
101
|
header: CompassSurveyHeader
|
|
102
102
|
shots: list[CompassShot] = Field(default_factory=list)
|
|
103
|
+
# Derived project-wide display metadata, never written into source files.
|
|
104
|
+
color: str | None = Field(default=None, exclude=True)
|
|
103
105
|
|
|
104
106
|
|
|
105
107
|
class CompassDatFile(BaseModel):
|
compass_lib-0.0.8/PKG-INFO
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: compass_lib
|
|
3
|
-
Version: 0.0.8
|
|
4
|
-
Summary: Compass Parser Library.
|
|
5
|
-
Keywords: cave,survey,karst
|
|
6
|
-
Author-email: Jonathan Dekhtiar <jonathan@dekhtiar.com>
|
|
7
|
-
Maintainer-email: Jonathan Dekhtiar <jonathan@dekhtiar.com>
|
|
8
|
-
Requires-Python: >=3.11
|
|
9
|
-
Description-Content-Type: text/markdown
|
|
10
|
-
Classifier: Development Status :: 3 - Alpha
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: Intended Audience :: Science/Research
|
|
13
|
-
Classifier: Intended Audience :: Information Technology
|
|
14
|
-
Classifier: Topic :: Software Development :: Build Tools
|
|
15
|
-
Classifier: Topic :: Scientific/Engineering
|
|
16
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
-
Classifier: Topic :: Utilities
|
|
18
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
19
|
-
Classifier: Operating System :: OS Independent
|
|
20
|
-
Classifier: Programming Language :: Python :: 3
|
|
21
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
-
License-File: LICENSE
|
|
23
|
-
Requires-Dist: geojson>=3.2,<4
|
|
24
|
-
Requires-Dist: numpy>=2.4.4,<3
|
|
25
|
-
Requires-Dist: scipy>=1.17.1,<2
|
|
26
|
-
Requires-Dist: orjson>=3.12,<4
|
|
27
|
-
Requires-Dist: pydantic>=2.13,<3
|
|
28
|
-
Requires-Dist: pydantic-extra-types>=2.11.1,<3
|
|
29
|
-
Requires-Dist: pyIGRF14>=1.0.4,<2
|
|
30
|
-
Requires-Dist: pyproj>=3.7.2,<4
|
|
31
|
-
Requires-Dist: shapely>=2.1.2,<3
|
|
32
|
-
Requires-Dist: utm>=0.8.1,<1
|
|
33
|
-
Requires-Dist: cryptography>=50.0.0,<51 ; extra == "test"
|
|
34
|
-
Requires-Dist: python-dotenv>=1.2.2,<2 ; extra == "test"
|
|
35
|
-
Requires-Dist: deepdiff>=7.0,<10 ; extra == "test"
|
|
36
|
-
Requires-Dist: pytest>=9.0.3,<10 ; extra == "test"
|
|
37
|
-
Requires-Dist: pytest-cov>=7.1.0,<8 ; extra == "test"
|
|
38
|
-
Requires-Dist: pytest-env>=1.6.0,<2 ; extra == "test"
|
|
39
|
-
Requires-Dist: pytest-runner>=6.0.1,<7 ; extra == "test"
|
|
40
|
-
Requires-Dist: pytest-ordering>=0.6,<1 ; extra == "test"
|
|
41
|
-
Requires-Dist: parameterized>=0.9.0,<1 ; extra == "test"
|
|
42
|
-
Project-URL: Bug Reports, https://github.com/OpenSpeleo/pytool_compass_lib/issues
|
|
43
|
-
Project-URL: Homepage, https://pypi.org/project/compass-lib/
|
|
44
|
-
Project-URL: Source, https://github.com/OpenSpeleo/pytool_compass_lib
|
|
45
|
-
Provides-Extra: test
|
|
46
|
-
Import-Name: compass_lib
|
|
47
|
-
|
|
48
|
-
# Compass Python Lib
|
|
49
|
-
|
|
50
|
-
## Conversion commands:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
# Install in dev mod
|
|
54
|
-
pip install -e ".[dev,test]"
|
|
55
|
-
|
|
56
|
-
# Install latest stable version
|
|
57
|
-
pip install compass_lib
|
|
58
|
-
|
|
59
|
-
# run some commands
|
|
60
|
-
compass convert --input_file=./tests/artifacts/fulford.dat --output_file=fulford.json --format=json --overwrite
|
|
61
|
-
compass convert --input_file=./tests/artifacts/random.dat --output_file=random.json --format=json --overwrite
|
|
62
|
-
```
|
|
63
|
-
|
compass_lib-0.0.8/README.md
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# Compass Python Lib
|
|
2
|
-
|
|
3
|
-
## Conversion commands:
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
# Install in dev mod
|
|
7
|
-
pip install -e ".[dev,test]"
|
|
8
|
-
|
|
9
|
-
# Install latest stable version
|
|
10
|
-
pip install compass_lib
|
|
11
|
-
|
|
12
|
-
# run some commands
|
|
13
|
-
compass convert --input_file=./tests/artifacts/fulford.dat --output_file=fulford.json --format=json --overwrite
|
|
14
|
-
compass convert --input_file=./tests/artifacts/random.dat --output_file=random.json --format=json --overwrite
|
|
15
|
-
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|