LoopStructural 1.6.15__py3-none-any.whl → 1.6.17__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.
Potentially problematic release.
This version of LoopStructural might be problematic. Click here for more details.
- LoopStructural/__init__.py +31 -12
- LoopStructural/interpolators/_geological_interpolator.py +7 -2
- LoopStructural/modelling/core/fault_topology.py +234 -0
- LoopStructural/modelling/core/geological_model.py +119 -119
- LoopStructural/modelling/core/stratigraphic_column.py +500 -0
- LoopStructural/modelling/features/builders/_fault_builder.py +1 -0
- LoopStructural/modelling/features/fault/_fault_segment.py +1 -1
- LoopStructural/utils/__init__.py +1 -0
- LoopStructural/utils/_surface.py +6 -1
- LoopStructural/utils/observer.py +150 -0
- LoopStructural/version.py +1 -1
- {loopstructural-1.6.15.dist-info → loopstructural-1.6.17.dist-info}/METADATA +1 -1
- {loopstructural-1.6.15.dist-info → loopstructural-1.6.17.dist-info}/RECORD +16 -13
- {loopstructural-1.6.15.dist-info → loopstructural-1.6.17.dist-info}/WHEEL +0 -0
- {loopstructural-1.6.15.dist-info → loopstructural-1.6.17.dist-info}/licenses/LICENSE +0 -0
- {loopstructural-1.6.15.dist-info → loopstructural-1.6.17.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
from typing import Dict, Optional, List, Tuple
|
|
3
|
+
import numpy as np
|
|
4
|
+
from LoopStructural.utils import rng, getLogger, Observable
|
|
5
|
+
logger = getLogger(__name__)
|
|
6
|
+
logger.info("Imported LoopStructural Stratigraphic Column module")
|
|
7
|
+
class UnconformityType(enum.Enum):
|
|
8
|
+
"""
|
|
9
|
+
An enumeration for different types of unconformities in a stratigraphic column.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
ERODE = 'erode'
|
|
13
|
+
ONLAP = 'onlap'
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class StratigraphicColumnElementType(enum.Enum):
|
|
17
|
+
"""
|
|
18
|
+
An enumeration for different types of elements in a stratigraphic column.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
UNIT = 'unit'
|
|
22
|
+
UNCONFORMITY = 'unconformity'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class StratigraphicColumnElement:
|
|
26
|
+
"""
|
|
27
|
+
A class to represent an element in a stratigraphic column, which can be a unit or a topological object
|
|
28
|
+
for example unconformity.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, uuid=None):
|
|
32
|
+
"""
|
|
33
|
+
Initializes the StratigraphicColumnElement with a uuid.
|
|
34
|
+
"""
|
|
35
|
+
if uuid is None:
|
|
36
|
+
import uuid as uuid_module
|
|
37
|
+
|
|
38
|
+
uuid = str(uuid_module.uuid4())
|
|
39
|
+
self.uuid = uuid
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class StratigraphicUnit(StratigraphicColumnElement):
|
|
43
|
+
"""
|
|
44
|
+
A class to represent a stratigraphic unit.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, *, uuid=None, name=None, colour=None, thickness=None, data=None):
|
|
48
|
+
"""
|
|
49
|
+
Initializes the StratigraphicUnit with a name and an optional description.
|
|
50
|
+
"""
|
|
51
|
+
super().__init__(uuid)
|
|
52
|
+
self.name = name
|
|
53
|
+
if colour is None:
|
|
54
|
+
colour = rng.random(3)
|
|
55
|
+
self.colour = colour
|
|
56
|
+
self.thickness = thickness
|
|
57
|
+
self.data = data
|
|
58
|
+
self.element_type = StratigraphicColumnElementType.UNIT
|
|
59
|
+
|
|
60
|
+
def to_dict(self):
|
|
61
|
+
"""
|
|
62
|
+
Converts the stratigraphic unit to a dictionary representation.
|
|
63
|
+
"""
|
|
64
|
+
colour = self.colour
|
|
65
|
+
if isinstance(colour, np.ndarray):
|
|
66
|
+
colour = colour.astype(float).tolist()
|
|
67
|
+
return {"name": self.name, "colour": colour, "thickness": self.thickness, 'uuid': self.uuid}
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def from_dict(cls, data):
|
|
71
|
+
"""
|
|
72
|
+
Creates a StratigraphicUnit from a dictionary representation.
|
|
73
|
+
"""
|
|
74
|
+
if not isinstance(data, dict):
|
|
75
|
+
raise TypeError("Data must be a dictionary")
|
|
76
|
+
name = data.get("name")
|
|
77
|
+
colour = data.get("colour")
|
|
78
|
+
thickness = data.get("thickness", None)
|
|
79
|
+
uuid = data.get("uuid", None)
|
|
80
|
+
return cls(uuid=uuid, name=name, colour=colour, thickness=thickness)
|
|
81
|
+
|
|
82
|
+
def __str__(self):
|
|
83
|
+
"""
|
|
84
|
+
Returns a string representation of the stratigraphic unit.
|
|
85
|
+
"""
|
|
86
|
+
return (
|
|
87
|
+
f"StratigraphicUnit(name={self.name}, colour={self.colour}, thickness={self.thickness})"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class StratigraphicUnconformity(StratigraphicColumnElement):
|
|
92
|
+
"""
|
|
93
|
+
A class to represent a stratigraphic unconformity, which is a surface of discontinuity in the stratigraphic record.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(
|
|
97
|
+
self, *, uuid=None, name=None, unconformity_type: UnconformityType = UnconformityType.ERODE
|
|
98
|
+
):
|
|
99
|
+
"""
|
|
100
|
+
Initializes the StratigraphicUnconformity with a name and an optional description.
|
|
101
|
+
"""
|
|
102
|
+
super().__init__(uuid)
|
|
103
|
+
|
|
104
|
+
self.name = name
|
|
105
|
+
if unconformity_type not in [UnconformityType.ERODE, UnconformityType.ONLAP]:
|
|
106
|
+
raise ValueError("Invalid unconformity type")
|
|
107
|
+
self.unconformity_type = unconformity_type
|
|
108
|
+
self.element_type = StratigraphicColumnElementType.UNCONFORMITY
|
|
109
|
+
|
|
110
|
+
def to_dict(self):
|
|
111
|
+
"""
|
|
112
|
+
Converts the stratigraphic unconformity to a dictionary representation.
|
|
113
|
+
"""
|
|
114
|
+
return {
|
|
115
|
+
"uuid": self.uuid,
|
|
116
|
+
"name": self.name,
|
|
117
|
+
"unconformity_type": self.unconformity_type.value,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
def __str__(self):
|
|
121
|
+
"""
|
|
122
|
+
Returns a string representation of the stratigraphic unconformity.
|
|
123
|
+
"""
|
|
124
|
+
return (
|
|
125
|
+
f"StratigraphicUnconformity(name={self.name}, "
|
|
126
|
+
f"unconformity_type={self.unconformity_type.value})"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def from_dict(cls, data):
|
|
131
|
+
"""
|
|
132
|
+
Creates a StratigraphicUnconformity from a dictionary representation.
|
|
133
|
+
"""
|
|
134
|
+
if not isinstance(data, dict):
|
|
135
|
+
raise TypeError("Data must be a dictionary")
|
|
136
|
+
name = data.get("name")
|
|
137
|
+
unconformity_type = UnconformityType(
|
|
138
|
+
data.get("unconformity_type", UnconformityType.ERODE.value)
|
|
139
|
+
)
|
|
140
|
+
uuid = data.get("uuid", None)
|
|
141
|
+
return cls(uuid=uuid, name=name, unconformity_type=unconformity_type)
|
|
142
|
+
class StratigraphicGroup:
|
|
143
|
+
"""
|
|
144
|
+
A class to represent a group of stratigraphic units.
|
|
145
|
+
This class is not fully implemented and serves as a placeholder for future development.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
def __init__(self, name=None, units=None):
|
|
149
|
+
"""
|
|
150
|
+
Initializes the StratigraphicGroup with a name and an optional list of units.
|
|
151
|
+
"""
|
|
152
|
+
self.name = name
|
|
153
|
+
self.units = units if units is not None else []
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class StratigraphicColumn(Observable['StratigraphicColumn']):
|
|
157
|
+
"""
|
|
158
|
+
A class to represent a stratigraphic column, which is a vertical section of the Earth's crust
|
|
159
|
+
showing the sequence of rock layers and their relationships.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def __init__(self):
|
|
163
|
+
"""
|
|
164
|
+
Initializes the StratigraphicColumn with a name and a list of layers.
|
|
165
|
+
"""
|
|
166
|
+
super().__init__()
|
|
167
|
+
self.order = [StratigraphicUnit(name='Basement', colour='grey', thickness=np.inf),StratigraphicUnconformity(name='Base Unconformity', unconformity_type=UnconformityType.ERODE)]
|
|
168
|
+
self.group_mapping = {}
|
|
169
|
+
def clear(self,basement=True):
|
|
170
|
+
"""
|
|
171
|
+
Clears the stratigraphic column, removing all elements.
|
|
172
|
+
"""
|
|
173
|
+
if basement:
|
|
174
|
+
self.order = [StratigraphicUnit(name='Basement', colour='grey', thickness=np.inf),StratigraphicUnconformity(name='Base Unconformity', unconformity_type=UnconformityType.ERODE)]
|
|
175
|
+
else:
|
|
176
|
+
self.order = []
|
|
177
|
+
self.group_mapping = {}
|
|
178
|
+
self.notify('column_cleared')
|
|
179
|
+
def add_unit(self, name,*, colour=None, thickness=None, where='top'):
|
|
180
|
+
unit = StratigraphicUnit(name=name, colour=colour, thickness=thickness)
|
|
181
|
+
|
|
182
|
+
if where == 'top':
|
|
183
|
+
self.order.append(unit)
|
|
184
|
+
elif where == 'bottom':
|
|
185
|
+
self.order.insert(0, unit)
|
|
186
|
+
else:
|
|
187
|
+
raise ValueError("Invalid 'where' argument. Use 'top' or 'bottom'.")
|
|
188
|
+
self.notify('unit_added', unit=unit)
|
|
189
|
+
return unit
|
|
190
|
+
|
|
191
|
+
def remove_unit(self, uuid):
|
|
192
|
+
"""
|
|
193
|
+
Removes a unit or unconformity from the stratigraphic column by its uuid.
|
|
194
|
+
"""
|
|
195
|
+
for i, element in enumerate(self.order):
|
|
196
|
+
if element.uuid == uuid:
|
|
197
|
+
del self.order[i]
|
|
198
|
+
self.notify('unit_removed', uuid=uuid)
|
|
199
|
+
return True
|
|
200
|
+
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
def add_unconformity(self, name, *, unconformity_type=UnconformityType.ERODE, where='top' ):
|
|
204
|
+
unconformity = StratigraphicUnconformity(
|
|
205
|
+
uuid=None, name=name, unconformity_type=unconformity_type
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if where == 'top':
|
|
209
|
+
self.order.append(unconformity)
|
|
210
|
+
elif where == 'bottom':
|
|
211
|
+
self.order.insert(0, unconformity)
|
|
212
|
+
else:
|
|
213
|
+
raise ValueError("Invalid 'where' argument. Use 'top' or 'bottom'.")
|
|
214
|
+
self.notify('unconformity_added', unconformity=unconformity)
|
|
215
|
+
return unconformity
|
|
216
|
+
|
|
217
|
+
def get_element_by_index(self, index):
|
|
218
|
+
"""
|
|
219
|
+
Retrieves an element by its index from the stratigraphic column.
|
|
220
|
+
"""
|
|
221
|
+
if index < 0 or index >= len(self.order):
|
|
222
|
+
raise IndexError("Index out of range")
|
|
223
|
+
return self.order[index]
|
|
224
|
+
|
|
225
|
+
def get_unit_by_name(self, name):
|
|
226
|
+
"""
|
|
227
|
+
Retrieves a unit by its name from the stratigraphic column.
|
|
228
|
+
"""
|
|
229
|
+
for unit in self.order:
|
|
230
|
+
if isinstance(unit, StratigraphicUnit) and unit.name == name:
|
|
231
|
+
return unit
|
|
232
|
+
|
|
233
|
+
return None
|
|
234
|
+
|
|
235
|
+
def get_unconformity_by_name(self, name):
|
|
236
|
+
"""
|
|
237
|
+
Retrieves an unconformity by its name from the stratigraphic column.
|
|
238
|
+
"""
|
|
239
|
+
for unconformity in self.order:
|
|
240
|
+
if isinstance(unconformity, StratigraphicUnconformity) and unconformity.name == name:
|
|
241
|
+
return unconformity
|
|
242
|
+
|
|
243
|
+
return None
|
|
244
|
+
def get_element_by_uuid(self, uuid):
|
|
245
|
+
"""
|
|
246
|
+
Retrieves an element by its uuid from the stratigraphic column.
|
|
247
|
+
"""
|
|
248
|
+
for element in self.order:
|
|
249
|
+
if element.uuid == uuid:
|
|
250
|
+
return element
|
|
251
|
+
raise KeyError(f"No element found with uuid: {uuid}")
|
|
252
|
+
|
|
253
|
+
def get_group_for_unit_name(self, unit_name:str) -> Optional[StratigraphicGroup]:
|
|
254
|
+
"""
|
|
255
|
+
Retrieves the group for a given unit name.
|
|
256
|
+
"""
|
|
257
|
+
for group in self.get_groups():
|
|
258
|
+
if any(unit.name == unit_name for unit in group.units):
|
|
259
|
+
return group
|
|
260
|
+
return None
|
|
261
|
+
def add_element(self, element):
|
|
262
|
+
"""
|
|
263
|
+
Adds a StratigraphicColumnElement to the stratigraphic column.
|
|
264
|
+
"""
|
|
265
|
+
if isinstance(element, StratigraphicColumnElement):
|
|
266
|
+
self.order.append(element)
|
|
267
|
+
else:
|
|
268
|
+
raise TypeError("Element must be an instance of StratigraphicColumnElement")
|
|
269
|
+
|
|
270
|
+
def get_elements(self):
|
|
271
|
+
"""
|
|
272
|
+
Returns a list of all elements in the stratigraphic column.
|
|
273
|
+
"""
|
|
274
|
+
return self.order
|
|
275
|
+
|
|
276
|
+
def get_groups(self):
|
|
277
|
+
groups = []
|
|
278
|
+
i=0
|
|
279
|
+
group = StratigraphicGroup(
|
|
280
|
+
name=(
|
|
281
|
+
f'Group_{i}'
|
|
282
|
+
if f'Group_{i}' not in self.group_mapping
|
|
283
|
+
else self.group_mapping[f'Group_{i}']
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
for e in reversed(self.order):
|
|
287
|
+
if isinstance(e, StratigraphicUnit):
|
|
288
|
+
group.units.append(e)
|
|
289
|
+
else:
|
|
290
|
+
if group.units:
|
|
291
|
+
groups.append(group)
|
|
292
|
+
i+=1
|
|
293
|
+
group = StratigraphicGroup(
|
|
294
|
+
name=(
|
|
295
|
+
f'Group_{i}'
|
|
296
|
+
if f'Group_{i}' not in self.group_mapping
|
|
297
|
+
else self.group_mapping[f'Group_{i}']
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
if group:
|
|
301
|
+
groups.append(group)
|
|
302
|
+
return groups
|
|
303
|
+
|
|
304
|
+
def get_unitname_groups(self):
|
|
305
|
+
groups = self.get_groups()
|
|
306
|
+
groups_list = []
|
|
307
|
+
group = []
|
|
308
|
+
for g in groups:
|
|
309
|
+
group = [u.name for u in g.units if isinstance(u, StratigraphicUnit)]
|
|
310
|
+
groups_list.append(group)
|
|
311
|
+
return groups_list
|
|
312
|
+
|
|
313
|
+
def get_group_unit_pairs(self) -> List[Tuple[str,str]]:
|
|
314
|
+
"""
|
|
315
|
+
Returns a list of tuples containing group names and unit names.
|
|
316
|
+
"""
|
|
317
|
+
groups = self.get_groups()
|
|
318
|
+
group_unit_pairs = []
|
|
319
|
+
for g in groups:
|
|
320
|
+
for u in g.units:
|
|
321
|
+
if isinstance(u, StratigraphicUnit):
|
|
322
|
+
group_unit_pairs.append((g.name, u.name))
|
|
323
|
+
return group_unit_pairs
|
|
324
|
+
|
|
325
|
+
def __getitem__(self, uuid):
|
|
326
|
+
"""
|
|
327
|
+
Retrieves an element by its uuid from the stratigraphic column.
|
|
328
|
+
"""
|
|
329
|
+
for element in self.order:
|
|
330
|
+
if element.uuid == uuid:
|
|
331
|
+
return element
|
|
332
|
+
raise KeyError(f"No element found with uuid: {uuid}")
|
|
333
|
+
|
|
334
|
+
def update_order(self, new_order):
|
|
335
|
+
"""
|
|
336
|
+
Updates the order of elements in the stratigraphic column based on a new order list.
|
|
337
|
+
"""
|
|
338
|
+
if not isinstance(new_order, list):
|
|
339
|
+
raise TypeError("New order must be a list")
|
|
340
|
+
self.order = [
|
|
341
|
+
self.__getitem__(uuid) for uuid in new_order if self.__getitem__(uuid) is not None
|
|
342
|
+
]
|
|
343
|
+
self.notify('order_updated', new_order=self.order)
|
|
344
|
+
|
|
345
|
+
def update_element(self, unit_data: Dict):
|
|
346
|
+
"""
|
|
347
|
+
Updates an existing element in the stratigraphic column with new data.
|
|
348
|
+
:param unit_data: A dictionary containing the updated data for the element.
|
|
349
|
+
"""
|
|
350
|
+
if not isinstance(unit_data, dict):
|
|
351
|
+
raise TypeError("unit_data must be a dictionary")
|
|
352
|
+
element = self.__getitem__(unit_data['uuid'])
|
|
353
|
+
if isinstance(element, StratigraphicUnit):
|
|
354
|
+
element.name = unit_data.get('name', element.name)
|
|
355
|
+
element.colour = unit_data.get('colour', element.colour)
|
|
356
|
+
element.thickness = unit_data.get('thickness', element.thickness)
|
|
357
|
+
elif isinstance(element, StratigraphicUnconformity):
|
|
358
|
+
element.name = unit_data.get('name', element.name)
|
|
359
|
+
element.unconformity_type = UnconformityType(
|
|
360
|
+
unit_data.get('unconformity_type', element.unconformity_type.value)
|
|
361
|
+
)
|
|
362
|
+
self.notify('element_updated', element=element)
|
|
363
|
+
|
|
364
|
+
def __str__(self):
|
|
365
|
+
"""
|
|
366
|
+
Returns a string representation of the stratigraphic column, listing all elements.
|
|
367
|
+
"""
|
|
368
|
+
return "\n".join([f"{i+1}. {element}" for i, element in enumerate(self.order)])
|
|
369
|
+
|
|
370
|
+
def to_dict(self):
|
|
371
|
+
"""
|
|
372
|
+
Converts the stratigraphic column to a dictionary representation.
|
|
373
|
+
"""
|
|
374
|
+
return {
|
|
375
|
+
"elements": [element.to_dict() for element in self.order],
|
|
376
|
+
}
|
|
377
|
+
def update_from_dict(self, data):
|
|
378
|
+
"""
|
|
379
|
+
Updates the stratigraphic column from a dictionary representation.
|
|
380
|
+
"""
|
|
381
|
+
if not isinstance(data, dict):
|
|
382
|
+
raise TypeError("Data must be a dictionary")
|
|
383
|
+
with self.freeze_notifications():
|
|
384
|
+
self.clear(basement=False)
|
|
385
|
+
elements_data = data.get("elements", [])
|
|
386
|
+
for element_data in elements_data:
|
|
387
|
+
if "unconformity_type" in element_data:
|
|
388
|
+
element = StratigraphicUnconformity.from_dict(element_data)
|
|
389
|
+
else:
|
|
390
|
+
element = StratigraphicUnit.from_dict(element_data)
|
|
391
|
+
self.add_element(element)
|
|
392
|
+
@classmethod
|
|
393
|
+
def from_dict(cls, data):
|
|
394
|
+
"""
|
|
395
|
+
Creates a StratigraphicColumn from a dictionary representation.
|
|
396
|
+
"""
|
|
397
|
+
if not isinstance(data, dict):
|
|
398
|
+
raise TypeError("Data must be a dictionary")
|
|
399
|
+
column = cls()
|
|
400
|
+
column.clear(basement=False)
|
|
401
|
+
elements_data = data.get("elements", [])
|
|
402
|
+
for element_data in elements_data:
|
|
403
|
+
if "unconformity_type" in element_data:
|
|
404
|
+
element = StratigraphicUnconformity.from_dict(element_data)
|
|
405
|
+
else:
|
|
406
|
+
element = StratigraphicUnit.from_dict(element_data)
|
|
407
|
+
column.add_element(element)
|
|
408
|
+
return column
|
|
409
|
+
|
|
410
|
+
def get_isovalues(self) -> Dict[str, float]:
|
|
411
|
+
"""
|
|
412
|
+
Returns a dictionary of isovalues for the stratigraphic units in the column.
|
|
413
|
+
"""
|
|
414
|
+
surface_values = {}
|
|
415
|
+
for g in reversed(self.get_groups()):
|
|
416
|
+
v = 0
|
|
417
|
+
for u in g.units:
|
|
418
|
+
surface_values[u.name] = {'value':v,'group':g.name,'colour':u.colour}
|
|
419
|
+
v += u.thickness
|
|
420
|
+
return surface_values
|
|
421
|
+
|
|
422
|
+
def plot(self,*, ax=None, **kwargs):
|
|
423
|
+
import matplotlib.pyplot as plt
|
|
424
|
+
from matplotlib import cm
|
|
425
|
+
from matplotlib.patches import Polygon
|
|
426
|
+
from matplotlib.collections import PatchCollection
|
|
427
|
+
n_units = 0 # count how many discrete colours (number of stratigraphic units)
|
|
428
|
+
xmin = 0
|
|
429
|
+
ymin = 0
|
|
430
|
+
ymax = 1
|
|
431
|
+
xmax = 1
|
|
432
|
+
fig = None
|
|
433
|
+
if ax is None:
|
|
434
|
+
fig, ax = plt.subplots(figsize=(2, 10))
|
|
435
|
+
patches = [] # stores the individual stratigraphic unit polygons
|
|
436
|
+
|
|
437
|
+
total_height = 0
|
|
438
|
+
prev_coords = [0, 0]
|
|
439
|
+
|
|
440
|
+
# iterate through groups, skipping faults
|
|
441
|
+
for u in reversed(self.order):
|
|
442
|
+
if u.element_type == StratigraphicColumnElementType.UNCONFORMITY:
|
|
443
|
+
logger.info(f"Plotting unconformity {u.name} of type {u.unconformity_type.value}")
|
|
444
|
+
ax.axhline(y=total_height, linestyle='--', color='black')
|
|
445
|
+
ax.annotate(
|
|
446
|
+
getattr(u, 'name', 'Unconformity'),
|
|
447
|
+
xy=(xmin, total_height),
|
|
448
|
+
fontsize=8,
|
|
449
|
+
ha='left',
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
total_height -= 0.05 # Adjust height slightly for visual separation
|
|
453
|
+
continue
|
|
454
|
+
|
|
455
|
+
if u.element_type == StratigraphicColumnElementType.UNIT:
|
|
456
|
+
logger.info(f"Plotting unit {u.name} of type {u.element_type}")
|
|
457
|
+
|
|
458
|
+
n_units += 1
|
|
459
|
+
|
|
460
|
+
ymax = total_height
|
|
461
|
+
ymin = ymax - (getattr(u, 'thickness', np.nan) if not np.isinf(getattr(u, 'thickness', np.nan)) else np.nanmean([getattr(e, 'thickness', np.nan) for e in self.order if not np.isinf(getattr(e, 'thickness', np.nan))]))
|
|
462
|
+
|
|
463
|
+
if not np.isfinite(ymin):
|
|
464
|
+
ymin = prev_coords[1] - (prev_coords[1] - prev_coords[0]) * (1 + rng.random())
|
|
465
|
+
|
|
466
|
+
total_height = ymin
|
|
467
|
+
|
|
468
|
+
prev_coords = (ymin, ymax)
|
|
469
|
+
|
|
470
|
+
polygon_points = np.array([[xmin, ymin], [xmax, ymin], [xmax, ymax], [xmin, ymax]])
|
|
471
|
+
patches.append(Polygon(polygon_points))
|
|
472
|
+
ax.annotate(getattr(u, 'name', 'Unknown'), xy=(xmin+(xmax-xmin)/2, (ymax-ymin)/2+ymin), fontsize=8, ha='left')
|
|
473
|
+
|
|
474
|
+
if 'cmap' not in kwargs:
|
|
475
|
+
import matplotlib.colors as colors
|
|
476
|
+
|
|
477
|
+
colours = []
|
|
478
|
+
boundaries = []
|
|
479
|
+
data = []
|
|
480
|
+
for i, u in enumerate(self.order):
|
|
481
|
+
if u.element_type != StratigraphicColumnElementType.UNIT:
|
|
482
|
+
continue
|
|
483
|
+
data.append((i, u.colour))
|
|
484
|
+
colours.append(u.colour)
|
|
485
|
+
boundaries.append(i) # print(u,v)
|
|
486
|
+
cmap = colors.ListedColormap(colours)
|
|
487
|
+
else:
|
|
488
|
+
cmap = cm.get_cmap(kwargs['cmap'], n_units - 1)
|
|
489
|
+
p = PatchCollection(patches, cmap=cmap)
|
|
490
|
+
|
|
491
|
+
colors = np.arange(len(patches))
|
|
492
|
+
p.set_array(np.array(colors))
|
|
493
|
+
|
|
494
|
+
ax.add_collection(p)
|
|
495
|
+
|
|
496
|
+
ax.set_ylim(total_height - (total_height - prev_coords[0]) * 0.1, 0)
|
|
497
|
+
|
|
498
|
+
ax.axis("off")
|
|
499
|
+
|
|
500
|
+
return fig
|
|
@@ -150,6 +150,7 @@ class FaultBuilder(StructuralFrameBuilder):
|
|
|
150
150
|
np.logical_and(fault_frame_data["coord"] == 0, fault_frame_data["val"] == 0),
|
|
151
151
|
["X", "Y"],
|
|
152
152
|
].to_numpy()
|
|
153
|
+
self.fault_dip = fault_dip
|
|
153
154
|
if fault_normal_vector is None:
|
|
154
155
|
if fault_frame_data.loc[
|
|
155
156
|
np.logical_and(fault_frame_data["coord"] == 0, fault_frame_data["nx"].notna())].shape[0]>0:
|
LoopStructural/utils/__init__.py
CHANGED
LoopStructural/utils/_surface.py
CHANGED
|
@@ -115,12 +115,17 @@ class LoopIsosurfacer:
|
|
|
115
115
|
values,
|
|
116
116
|
)
|
|
117
117
|
logger.info(f'Isosurfacing at values: {isovalues}')
|
|
118
|
+
individual_names = False
|
|
118
119
|
if name is None:
|
|
119
120
|
names = ["surface"] * len(isovalues)
|
|
120
121
|
if isinstance(name, str):
|
|
121
122
|
names = [name] * len(isovalues)
|
|
123
|
+
if len(isovalues) == 1:
|
|
124
|
+
individual_names = True
|
|
122
125
|
if isinstance(name, list):
|
|
123
126
|
names = name
|
|
127
|
+
if len(names) == len(isovalues):
|
|
128
|
+
individual_names = True
|
|
124
129
|
if colours is None:
|
|
125
130
|
colours = [None] * len(isovalues)
|
|
126
131
|
for name, isovalue, colour in zip(names, isovalues, colours):
|
|
@@ -151,7 +156,7 @@ class LoopIsosurfacer:
|
|
|
151
156
|
vertices=verts,
|
|
152
157
|
triangles=faces,
|
|
153
158
|
normals=normals,
|
|
154
|
-
name=f"{name}_{isovalue}",
|
|
159
|
+
name=name if individual_names else f"{name}_{isovalue}",
|
|
155
160
|
values=values,
|
|
156
161
|
colour=colour,
|
|
157
162
|
)
|