forgeo-gmlib 0.6.2__cp312-cp312-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,249 @@
1
+ #
2
+ # This file is part of gmlib. It is free software.
3
+ # You can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3.
4
+ #
5
+
6
+ import copy
7
+
8
+ import numpy as np
9
+
10
+ import forgeo.gmlib.pypotential3D as potential
11
+ from forgeo.gmlib.common import OrientedEvaluation
12
+
13
+ try:
14
+ from skimage.measure import marching_cubes
15
+ except ImportError:
16
+ raise
17
+
18
+ try:
19
+ from pycgal.Polygon_mesh_processing import corefine
20
+ from pycgal.Surface_mesh import Surface_mesh
21
+ except ImportError:
22
+ raise
23
+
24
+
25
+ # FIXME: to be moved elsewhere
26
+ def zmap_to_tsurf(zmap, origin, steps):
27
+ nx, ny = zmap.shape
28
+ assert nx > 1
29
+ assert ny > 1
30
+ xy = np.vstack(
31
+ [
32
+ a.ravel()
33
+ for a in np.meshgrid(
34
+ origin[0] + steps[0] * np.arange(0, nx),
35
+ origin[1] + steps[1] * np.arange(0, ny),
36
+ )
37
+ ]
38
+ )
39
+ vertices = np.vstack([xy, zmap.ravel(order="F")])
40
+ vertices = np.transpose(vertices)
41
+ tmp = np.hstack(
42
+ [
43
+ np.reshape(a, (-1, 1))
44
+ for a in [
45
+ np.arange(0, nx - 1),
46
+ np.arange(1, nx),
47
+ nx + np.arange(0, nx - 1), # triangle 1
48
+ np.arange(1, nx),
49
+ nx + np.arange(1, nx),
50
+ nx + np.arange(0, nx - 1), # triangle 2
51
+ ]
52
+ ]
53
+ )
54
+ triangles = np.vstack([tmp + k * nx for k in range(ny - 1)])
55
+ triangles.shape = (-1, 3)
56
+ return Surface_mesh(vertices, triangles)
57
+
58
+
59
+ def _remove_faces_on_side(surface, side, centroid_value):
60
+ if side < 0:
61
+ removed_faces = [f for f, cv in zip(surface.faces(), centroid_value) if cv > 0]
62
+ else:
63
+ removed_faces = [f for f, cv in zip(surface.faces(), centroid_value) if cv < 0]
64
+ for f in removed_faces:
65
+ surface.remove_face(f)
66
+
67
+
68
+ class TopographyClipper:
69
+ def __init__(self, field, tesselation):
70
+ self.field = field
71
+ self.tesselation = tesselation
72
+
73
+ def clip(self, surface):
74
+ corefine(self.tesselation, surface)
75
+ self.field(surface.centroids())
76
+ _remove_faces_on_side(surface, 1, self.field(surface.centroids()))
77
+ return surface
78
+
79
+
80
+ class Tesselator:
81
+ def __init__(self, box, shape):
82
+ self.reset(box, shape)
83
+
84
+ def reset(self, box, shape):
85
+ assert all(n > 0 for n in shape)
86
+ nx, ny, nz = shape
87
+ steps = (
88
+ np.linspace(box.xmin, box.xmax, nx),
89
+ np.linspace(box.ymin, box.ymax, ny),
90
+ np.linspace(box.zmin, box.zmax, nz),
91
+ )
92
+ coordinates = np.meshgrid(*steps, indexing="ij")
93
+ points = np.stack(coordinates, axis=-1)
94
+ points.shape = (-1, 3)
95
+ self.box = box
96
+ self.shape = shape
97
+ self.points = points
98
+
99
+ def rescale(self, pts):
100
+ box = self.box
101
+ nx, ny, nz = self.shape
102
+ return pts * np.array(
103
+ [
104
+ (box.xmax - box.xmin) / (nx - 1),
105
+ (box.ymax - box.ymin) / (ny - 1),
106
+ (box.zmax - box.zmin) / (nz - 1),
107
+ ]
108
+ ) + np.array([box.xmin, box.ymin, box.zmin])
109
+
110
+ def tesselate(f, v):
111
+ return self(f, v)
112
+
113
+ def __call__(self, f, v):
114
+ field_value = f(self.points)
115
+ field_value.shape = self.shape
116
+ isocontour = marching_cubes(field_value, level=v)
117
+ verts, faces, _normals, _values = isocontour
118
+ verts = self.rescale(verts)
119
+ return Surface_mesh(verts, faces)
120
+
121
+ def tesselate_topography(self, model):
122
+ assert model.topography is not None
123
+ topography = model.topography
124
+ zmap = topography.z
125
+ if hasattr(zmap, "shape"):
126
+ return zmap_to_tsurf(zmap, topography.origin, topography.steps)
127
+ return self(topography, 0)
128
+
129
+ def tesselate_faults(self, model, topography=None):
130
+ fault_tesselations = {
131
+ name: self(field, 0) for name, field in model.faults.items()
132
+ }
133
+ for name, surface in fault_tesselations.items():
134
+ if topography:
135
+ surface = topography.clip(surface)
136
+ data = model.faults_data[name]
137
+ assert len(data.potential_data.interfaces) == 1
138
+ fault_points = data.potential_data.interfaces[0]
139
+ for limit_name in data.stops_on:
140
+ try:
141
+ limit_surface = fault_tesselations[limit_name]
142
+ corefine(surface, limit_surface)
143
+ limit_fault = model.faults[limit_name]
144
+ _remove_faces_on_side(
145
+ surface,
146
+ np.mean(limit_fault(fault_points)),
147
+ limit_fault(surface.centroids()),
148
+ )
149
+ except KeyError:
150
+ pass
151
+ return fault_tesselations
152
+
153
+ def tesselate_interfaces(self, model, topography=None):
154
+ fault_tesselations = self.tesselate_faults(model, topography)
155
+ interface_tesselations = {}
156
+ for serie in model.series_info:
157
+ sinfo = model.series_info[serie]
158
+ serie_faults_names = list(sinfo.active_faults)
159
+ serie_faults = [model.faults[name] for name in serie_faults_names]
160
+ faults_limits = [
161
+ [
162
+ serie_faults_names.index(limit)
163
+ for limit in model.faults_data[name].stops_on
164
+ ]
165
+ for name in serie_faults_names
166
+ ]
167
+ fault_network = gmlib.fault_network.build(serie_faults, faults_limits)
168
+ # for interface in sinfo.interfaces:
169
+ # name, _ = interface
170
+ # interface_tesselations[name] = []
171
+ if fault_network is not None:
172
+ nodes, _roots, evaluations = fault_network
173
+ node_tesselation = [
174
+ fault_tesselations[fault] for fault in serie_faults_names
175
+ ]
176
+ # fault drift indexes
177
+ fdi = [sinfo.active_faults[fault] for fault in serie_faults_names]
178
+ interface_patches = {name: [] for name, _ in sinfo.interfaces}
179
+ for evaluation in evaluations:
180
+ drifts = copy.copy(sinfo.drifts)
181
+ for fk, side in enumerate(evaluation):
182
+ if side > 0:
183
+ orientation = OrientedEvaluation.always_positive
184
+ elif side < 0:
185
+ orientation = OrientedEvaluation.always_negative
186
+ else:
187
+ assert side == 0
188
+ orientation = OrientedEvaluation.always_outside
189
+ drifts[fdi[fk]] = potential.make_drift(
190
+ nodes[fk].fault, orientation
191
+ )
192
+ S = potential.alternate_drifts(sinfo.field, drifts)
193
+ for interface in sinfo.interfaces:
194
+ interface_name, interface_value = interface
195
+ patch_tesselation = self(S, interface_value)
196
+ if topography:
197
+ topography.clip(patch_tesselation)
198
+ for fk, side in enumerate(evaluation):
199
+ if side != 0:
200
+ corefine(patch_tesselation, node_tesselation[fk])
201
+ nk = nodes[fk]
202
+ _remove_faces_on_side(
203
+ patch_tesselation,
204
+ side,
205
+ nk.fault(patch_tesselation.centroids()),
206
+ )
207
+ interface_patches[interface_name].append(patch_tesselation)
208
+ for interface_name, patches in interface_patches.items():
209
+ assert patches
210
+ tesselation = Surface_mesh(patches[0])
211
+ for patch in patches[1:]:
212
+ tesselation.join(patch)
213
+ interface_tesselations[interface_name] = tesselation
214
+ else: # serie is not affected by faults
215
+ for interface in sinfo.interfaces:
216
+ interface_name, interface_value = interface
217
+ tesselation = self(sinfo.field, interface_value)
218
+ if topography:
219
+ topography.clip(tesselation)
220
+ interface_tesselations[interface_name] = tesselation
221
+ return fault_tesselations, interface_tesselations
222
+
223
+
224
+ def tesselate_topography(box, shape, model):
225
+ return Tesselator(box, shape).tesselate_topography(model)
226
+
227
+
228
+ def tesselate_faults(box, shape, model, clip_topography=True):
229
+ tesselator = Tesselator(box, shape)
230
+ topography = (
231
+ TopographyClipper(model.topography, tesselator.tesselate_topography(model))
232
+ if clip_topography
233
+ else None
234
+ )
235
+ return tesselator.tesselate_faults(model, topography)
236
+
237
+
238
+ def tesselate_interfaces(box, shape, model, clip_topography=True):
239
+ tesselator = Tesselator(box, shape)
240
+ topography = (
241
+ TopographyClipper(model.topography, tesselator.tesselate_topography(model))
242
+ if clip_topography
243
+ else None
244
+ )
245
+ faults, interfaces = tesselator.tesselate_interfaces(model, topography)
246
+ if topography:
247
+ assert "topography" not in interfaces
248
+ interfaces["topography"] = topography.tesselation
249
+ return faults, interfaces
@@ -0,0 +1,198 @@
1
+ #
2
+ # This file is part of gmlib. It is free software.
3
+ # You can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3.
4
+ #
5
+
6
+ import os
7
+
8
+ import numpy as np
9
+
10
+ from .utils.tools import BBox3
11
+
12
+
13
+ class ImplicitElevationSurface:
14
+ def __call__(self, P):
15
+ P = np.reshape(np.asarray(P), (-1, 3))
16
+ res = P[:, 2] - self.evaluate_z(P[:, :2])
17
+ assert res.shape[0] > 0
18
+ if res.shape[0] == 1:
19
+ return res[0]
20
+ return res
21
+
22
+ def underlying_dtm(self):
23
+ return None
24
+
25
+
26
+ class ImplicitHorizontalPlane(ImplicitElevationSurface):
27
+ def __init__(self, zvalue):
28
+ self.z = float(zvalue)
29
+
30
+ def evaluate_z(self, P):
31
+ P = np.reshape(np.asarray(P), (-1, 2))
32
+ assert P.shape[0] > 0
33
+ if P.shape[0] == 1:
34
+ return self.z
35
+ return np.tile(self.z, P.shape[0])
36
+
37
+ def bbox(self):
38
+ return BBox3(zmin=self.z, zmax=self.z)
39
+
40
+
41
+ class ImplicitDTM(ImplicitElevationSurface):
42
+ def __init__(self, origin, steps, zmap):
43
+ self.origin = np.array(origin, dtype="d")
44
+ assert self.origin.shape == (2,)
45
+ self.steps = np.array(steps, copy=True, dtype="d")
46
+ self.invsteps = np.array([(1.0 / ds) for ds in steps], dtype="d")
47
+ assert self.invsteps.shape == (2,)
48
+ self.z = np.array(zmap, copy=True, dtype="d")
49
+
50
+ def bbox(self):
51
+ Ox, Oy = self.origin
52
+ ny, nx = self.z.shape
53
+ dx, dy = self.steps
54
+ return BBox3(Ox, Ox + nx * dx, Oy, Oy + ny * dy, self.z.min(), self.z.max())
55
+
56
+ def in_square_interpolation(self, P):
57
+ P = np.asarray(P, dtype="d")
58
+ assert P.shape == (2,)
59
+ DX = P - self.origin
60
+ DX *= self.invsteps
61
+ DX[DX < 0] = 0
62
+ i, j = DX
63
+ zmap = self.z
64
+ nx, ny = zmap.shape
65
+ i = min(i, nx - 1)
66
+ j = min(j, ny - 1)
67
+ ii, ij = int(i), int(j)
68
+ i -= ii
69
+ j -= ij
70
+ if j == 0:
71
+ if i == 0:
72
+ return zmap[ii, ij]
73
+ zl, zr = zmap[ii : ii + 2, ij]
74
+ return (1 - i) * zl + i * zr
75
+ if i == 0:
76
+ zl, zr = zmap[ii, ij : ij + 2]
77
+ return (1 - j) * zl + j * zr
78
+ # We divide the square into two triangles
79
+ # to be consistent with the DTM 3D representation
80
+ # and possible topography clipping
81
+ assert 0 < i < 1
82
+ assert 0 < j < 1
83
+ if i + j <= 1: # lower triangle
84
+ z = (1 - i - j) * zmap[ii, ij]
85
+ z += i * zmap[ii + 1, ij]
86
+ z += j * zmap[ii, ij + 1]
87
+ else: # upper triangle
88
+ z = (i + j - 1) * zmap[ii + 1, ij + 1]
89
+ z += (1 - i) * zmap[ii, ij + 1]
90
+ z += (1 - j) * zmap[ii + 1, ij]
91
+ return z
92
+ raise AssertionError()
93
+
94
+ def underlying_dtm(self):
95
+ zmap = self.z
96
+ nx, ny = zmap.shape
97
+ dx, dy = self.steps
98
+ x = np.arange(0, nx * dx, dx) + self.origin[0]
99
+ y = np.arange(0, ny * dy, dy) + self.origin[1]
100
+ xy = np.meshgrid(x, y, indexing="ij")
101
+ vertices = np.empty((nx * ny, 3), dtype=zmap.dtype)
102
+ for i, xi in enumerate(xy):
103
+ vertices[:, i] = xi.ravel()
104
+ vertices[:, -1] = zmap.ravel()
105
+ # Two triangles in the lower left cell
106
+ llc = np.array([[0, ny, 1], [ny, ny + 1, 1]], dtype=np.int64)
107
+ # Triangles stip on the left side
108
+ strip = np.vstack([llc + k for k in range(ny - 1)])
109
+ triangles = np.vstack([strip + ny * k for k in range(nx - 1)])
110
+ return vertices, triangles
111
+
112
+ def evaluate_z(self, P):
113
+ P = np.reshape(np.asarray(P), (-1, 2))
114
+ res = np.array([self.in_square_interpolation(xy) for xy in P])
115
+ try:
116
+ return float(res)
117
+ except TypeError:
118
+ pass
119
+ return res
120
+
121
+
122
+ def extract_plane(f):
123
+ # we already have read the code character (first character on the line)
124
+ l = f.readline().strip().split()
125
+ point = tuple(float(s) for s in l[:3])
126
+ normal = tuple(float(s) for s in l[3:6])
127
+ # ux = tuple(float(s) for s in l[6:9])
128
+ # uy = tuple(float(s) for s in l[9:]:)
129
+ return point, normal
130
+
131
+
132
+ def extract_mnt(f, decimals=8):
133
+ # we already have read the code character (first character on the line)
134
+ l = f.readline().strip().split()
135
+ nx, ny = (int(s) for s in l[6:8])
136
+ assert int(l[8]) + 2 == nx
137
+ assert int(l[9]) + 2 == ny
138
+ zmap = []
139
+ line = l[10:]
140
+ while line:
141
+ zmap.append(np.array([float(s) for s in line]))
142
+ line = f.readline().strip().split()
143
+ zmap = np.array(zmap)
144
+ x, y, z = (zmap[:, k::3] for k in range(3))
145
+
146
+ def clean(a):
147
+ a = np.delete(a, [1, nx - 2], axis=0)
148
+ return np.delete(a, [1, ny - 2], axis=1)
149
+
150
+ x, y, z = (clean(a) for a in (x, y, z))
151
+ dx = np.unique(np.round(x[1:, :] - x[:-1, :], decimals))
152
+ assert dx.shape == (1,)
153
+ dy = np.unique(np.round(y[:, 1:] - y[:, :-1], decimals))
154
+ assert dy.shape == (1,)
155
+ xmin, ymin = x.min(), y.min()
156
+ return (xmin, ymin), (float(dx), float(dy)), z
157
+
158
+
159
+ def sec_extract(filename):
160
+ if os.path.exists(filename):
161
+ with open(filename) as f:
162
+ line = f.readline()
163
+ while not line.startswith("Surfaces"):
164
+ line = f.readline()
165
+ code = int(f.read(1))
166
+ if code == 1:
167
+ point, normal = extract_plane(f)
168
+ assert normal[0] == 0
169
+ assert normal[1] == 0
170
+ return ImplicitHorizontalPlane(point[2])
171
+ if code == 9:
172
+ return ImplicitDTM(*extract_mnt(f))
173
+ raise OSError(filename + " not found")
174
+
175
+
176
+ if __name__ == "__main__":
177
+ from matplotlib import pyplot as plt
178
+
179
+ zmap = np.array([[0, 1], [0, 2]])
180
+ origin = (1, 1)
181
+ steps = (2, 2)
182
+ dtm = ImplicitDTM(origin, steps, zmap)
183
+ nx, ny = 20, 20
184
+ xmin, xmax = 0, 4
185
+ ymin, ymax = 0, 4
186
+ xy = np.meshgrid(np.linspace(xmin, xmax, nx), np.linspace(ymin, ymax, ny))
187
+ x, y = (a.ravel() for a in xy)
188
+ z = np.array([dtm.evaluate_z((xi, yi)) for xi, yi in zip(x, y)])
189
+ z.shape = nx, ny
190
+ plt.gca().set_aspect("equal")
191
+ box = (xmin, xmax, ymin, ymax)
192
+ plt.imshow(np.transpose(z)[::-1], extent=box)
193
+ O = origin
194
+ d = steps
195
+ cell = np.array(
196
+ [O, (O[0] + d[0], O[0]), (O[0] + d[0], O[0] + d[1]), (O[0], O[0] + d[1]), O]
197
+ )
198
+ plt.plot(cell[:, 0], cell[:, 1], "r")
File without changes