ncftools 0.9.2__tar.gz → 0.9.3__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.
- {ncftools-0.9.2 → ncftools-0.9.3}/PKG-INFO +1 -1
- {ncftools-0.9.2 → ncftools-0.9.3}/ncftools/__init__.py +1 -1
- ncftools-0.9.3/ncftools/meshinfo.py +212 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/ncftools.egg-info/PKG-INFO +1 -1
- {ncftools-0.9.2 → ncftools-0.9.3}/pyproject.toml +1 -1
- ncftools-0.9.2/ncftools/meshinfo.py +0 -110
- {ncftools-0.9.2 → ncftools-0.9.3}/MANIFEST.in +0 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/README.md +0 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/ncftools/cli.py +0 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/ncftools/describe.py +0 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/ncftools/nc2shp.py +0 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/ncftools/rnxml.py +0 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/ncftools/setncrain.py +0 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/ncftools/tests/__init__.py +0 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/ncftools/transzone1.py +0 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/ncftools/transzone2.py +0 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/ncftools.egg-info/SOURCES.txt +0 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/ncftools.egg-info/dependency_links.txt +0 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/ncftools.egg-info/entry_points.txt +0 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/ncftools.egg-info/requires.txt +0 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/ncftools.egg-info/top_level.txt +0 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/requirements.txt +0 -0
- {ncftools-0.9.2 → ncftools-0.9.3}/setup.cfg +0 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
meshinfo - Display mesh information from FlowFM NetCDF files
|
|
4
|
+
Uses netCDF4 Python bindings
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
import netCDF4 as nc
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
MAX_CELL_NODES = 6 # D-Flow FM only treats closed polygons up to 6 nodes as cells
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def find_net_cells(x, y, links):
|
|
19
|
+
"""
|
|
20
|
+
Derive the 2D cells of an old-format net file from its node-link graph.
|
|
21
|
+
|
|
22
|
+
Traces every face of the planar graph by walking half-edges with the face
|
|
23
|
+
kept on the left. Counter-clockwise faces of at most MAX_CELL_NODES nodes
|
|
24
|
+
are cells; the outer boundary and unmeshed holes are not.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
x, y (ndarray): Node coordinates
|
|
28
|
+
links (ndarray): (nLinks, 2) zero-based node indices
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
ndarray: Number of nodes of each cell
|
|
32
|
+
"""
|
|
33
|
+
n_links = len(links)
|
|
34
|
+
if n_links == 0:
|
|
35
|
+
return np.array([], dtype=int)
|
|
36
|
+
|
|
37
|
+
src = np.concatenate([links[:, 0], links[:, 1]])
|
|
38
|
+
dst = np.concatenate([links[:, 1], links[:, 0]])
|
|
39
|
+
twin = np.concatenate([np.arange(n_links, 2 * n_links), np.arange(n_links)])
|
|
40
|
+
|
|
41
|
+
# Sort half-edges around each origin node by direction (counter-clockwise)
|
|
42
|
+
angle = np.arctan2(y[dst] - y[src], x[dst] - x[src])
|
|
43
|
+
order = np.lexsort((angle, src))
|
|
44
|
+
rank = np.empty_like(order)
|
|
45
|
+
rank[order] = np.arange(len(order))
|
|
46
|
+
degree = np.bincount(src, minlength=len(x))
|
|
47
|
+
start = np.concatenate([[0], np.cumsum(degree)[:-1]])
|
|
48
|
+
|
|
49
|
+
# Next half-edge in the face: the one preceding the twin around the end node
|
|
50
|
+
s = start[dst]
|
|
51
|
+
nxt = order[s + (rank[twin] - s - 1) % degree[dst]]
|
|
52
|
+
|
|
53
|
+
# Label each face cycle by its smallest half-edge index (pointer jumping)
|
|
54
|
+
label = np.arange(len(nxt))
|
|
55
|
+
jump = nxt
|
|
56
|
+
for _ in range(int(np.ceil(np.log2(len(nxt)))) + 1):
|
|
57
|
+
label = np.minimum(label, label[jump])
|
|
58
|
+
jump = jump[jump]
|
|
59
|
+
|
|
60
|
+
n_nodes = np.bincount(label, minlength=len(nxt))
|
|
61
|
+
area2 = np.bincount(label, weights=x[src] * y[dst] - x[dst] * y[src],
|
|
62
|
+
minlength=len(nxt))
|
|
63
|
+
is_cell = (n_nodes >= 3) & (n_nodes <= MAX_CELL_NODES) & (area2 > 0)
|
|
64
|
+
return n_nodes[is_cell]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _read_ugrid(dataset):
|
|
68
|
+
"""Read counts and coordinates from a UGRID (Mesh2d_*) file."""
|
|
69
|
+
info = {
|
|
70
|
+
'nodes': dataset.dimensions['Mesh2d_nNodes'].size,
|
|
71
|
+
'faces': dataset.dimensions['Mesh2d_nFaces'].size,
|
|
72
|
+
'edges': dataset.dimensions['Mesh2d_nEdges'].size,
|
|
73
|
+
'face_sizes': None,
|
|
74
|
+
'x': None,
|
|
75
|
+
'y': None,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if 'Mesh2d_face_nodes' in dataset.variables:
|
|
79
|
+
face_nodes = dataset.variables['Mesh2d_face_nodes'][:]
|
|
80
|
+
fill_value = dataset.variables['Mesh2d_face_nodes']._FillValue
|
|
81
|
+
info['face_sizes'] = np.sum(face_nodes != fill_value, axis=1)
|
|
82
|
+
|
|
83
|
+
if 'Mesh2d_node_x' in dataset.variables and 'Mesh2d_node_y' in dataset.variables:
|
|
84
|
+
info['x'] = dataset.variables['Mesh2d_node_x'][:]
|
|
85
|
+
info['y'] = dataset.variables['Mesh2d_node_y'][:]
|
|
86
|
+
|
|
87
|
+
return info
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _read_net(dataset):
|
|
91
|
+
"""Read counts and coordinates from an old-format (NetNode/NetLink) net file."""
|
|
92
|
+
x = np.asarray(dataset.variables['NetNode_x'][:], dtype=float)
|
|
93
|
+
y = np.asarray(dataset.variables['NetNode_y'][:], dtype=float)
|
|
94
|
+
links = np.asarray(dataset.variables['NetLink'][:], dtype=np.int64)
|
|
95
|
+
start_index = getattr(dataset.variables['NetLink'], 'start_index', 1)
|
|
96
|
+
face_sizes = find_net_cells(x, y, links - start_index)
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
'nodes': len(x),
|
|
100
|
+
'faces': len(face_sizes),
|
|
101
|
+
'edges': len(links),
|
|
102
|
+
'face_sizes': face_sizes,
|
|
103
|
+
'x': x,
|
|
104
|
+
'y': y,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def show_mesh_info(nc_file):
|
|
109
|
+
"""
|
|
110
|
+
Display mesh information from a FlowFM NetCDF file.
|
|
111
|
+
|
|
112
|
+
Supports both UGRID map/net files (Mesh2d_* variables) and old-format
|
|
113
|
+
net files (NetNode_x/NetNode_y/NetLink), whose cells are derived from
|
|
114
|
+
the links.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
nc_file (str): Path to the FlowFM NetCDF mesh file
|
|
118
|
+
"""
|
|
119
|
+
with nc.Dataset(nc_file, 'r') as dataset:
|
|
120
|
+
if 'Mesh2d_nNodes' in dataset.dimensions:
|
|
121
|
+
info = _read_ugrid(dataset)
|
|
122
|
+
derived = False
|
|
123
|
+
elif all(v in dataset.variables for v in ('NetNode_x', 'NetNode_y', 'NetLink')):
|
|
124
|
+
info = _read_net(dataset)
|
|
125
|
+
derived = True
|
|
126
|
+
else:
|
|
127
|
+
raise ValueError('no Mesh2d_* or NetNode/NetLink variables found')
|
|
128
|
+
|
|
129
|
+
print(f"FlowFM Mesh Information from: {nc_file}")
|
|
130
|
+
print("=" * 60)
|
|
131
|
+
|
|
132
|
+
nodes, faces, edges = info['nodes'], info['faces'], info['edges']
|
|
133
|
+
suffix = " (derived from NetLink)" if derived else ""
|
|
134
|
+
|
|
135
|
+
print(f"Number of mesh nodes: {nodes:,}")
|
|
136
|
+
print(f"Number of mesh faces: {faces:,}{suffix}")
|
|
137
|
+
print(f"Number of mesh edges: {edges:,}")
|
|
138
|
+
|
|
139
|
+
if info['face_sizes'] is not None:
|
|
140
|
+
valid_nodes = info['face_sizes']
|
|
141
|
+
|
|
142
|
+
triangles = int(np.sum(valid_nodes == 3))
|
|
143
|
+
quads = int(np.sum(valid_nodes == 4))
|
|
144
|
+
others = int(np.sum(valid_nodes > 4))
|
|
145
|
+
|
|
146
|
+
print("\nElement Types:")
|
|
147
|
+
print(f" Triangular elements: {triangles:,}")
|
|
148
|
+
print(f" Quadrilateral elements: {quads:,}")
|
|
149
|
+
if others:
|
|
150
|
+
print(f" 5-6 sided elements: {others:,}")
|
|
151
|
+
print(f" Total elements: {triangles + quads + others:,}")
|
|
152
|
+
|
|
153
|
+
if info['x'] is not None and len(info['x']):
|
|
154
|
+
x_coords, y_coords = info['x'], info['y']
|
|
155
|
+
|
|
156
|
+
print("\nSpatial extent:")
|
|
157
|
+
print(f" X range: {np.min(x_coords):.1f} to {np.max(x_coords):.1f}")
|
|
158
|
+
print(f" Y range: {np.min(y_coords):.1f} to {np.max(y_coords):.1f}")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def main():
|
|
162
|
+
parser = argparse.ArgumentParser(
|
|
163
|
+
prog='meshinfo',
|
|
164
|
+
description='Display mesh information from FlowFM NetCDF files',
|
|
165
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
166
|
+
epilog="""
|
|
167
|
+
Examples:
|
|
168
|
+
meshinfo # Use default FlowFM_net.nc
|
|
169
|
+
meshinfo grid.nc # Use any FlowFM mesh file
|
|
170
|
+
meshinfo -i grid.nc # Same, with the -i flag
|
|
171
|
+
meshinfo -h # Show this help message
|
|
172
|
+
|
|
173
|
+
The tool displays:
|
|
174
|
+
- Number of nodes, faces, and edges
|
|
175
|
+
- Element type distribution (triangles vs quadrilaterals)
|
|
176
|
+
- Spatial extent (X and Y coordinate ranges)
|
|
177
|
+
""",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
parser.add_argument(
|
|
181
|
+
'file',
|
|
182
|
+
nargs='?',
|
|
183
|
+
metavar='FILE',
|
|
184
|
+
help='Path to the FlowFM NetCDF mesh file (default: FlowFM_net.nc)',
|
|
185
|
+
)
|
|
186
|
+
parser.add_argument(
|
|
187
|
+
'-i', '--input',
|
|
188
|
+
metavar='FILE',
|
|
189
|
+
help='Same as FILE (kept for backward compatibility)',
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
args = parser.parse_args()
|
|
193
|
+
|
|
194
|
+
if args.file and args.input:
|
|
195
|
+
parser.error('give the mesh file either as FILE or with -i, not both')
|
|
196
|
+
args.input = args.file or args.input or 'FlowFM_net.nc'
|
|
197
|
+
|
|
198
|
+
if not os.path.isfile(args.input):
|
|
199
|
+
print(f"Error: File '{args.input}' not found.")
|
|
200
|
+
print(f"Current directory: {os.getcwd()}")
|
|
201
|
+
print("Please check the file path and try again.")
|
|
202
|
+
sys.exit(1)
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
show_mesh_info(args.input)
|
|
206
|
+
except Exception as e:
|
|
207
|
+
print(f"Error reading NetCDF file: {e}")
|
|
208
|
+
sys.exit(1)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
if __name__ == "__main__":
|
|
212
|
+
main()
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
meshinfo - Display mesh information from FlowFM NetCDF files
|
|
4
|
-
Uses netCDF4 Python bindings
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import argparse
|
|
8
|
-
import os
|
|
9
|
-
import sys
|
|
10
|
-
|
|
11
|
-
import netCDF4 as nc
|
|
12
|
-
import numpy as np
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def show_mesh_info(nc_file):
|
|
16
|
-
"""
|
|
17
|
-
Display mesh information from a FlowFM NetCDF file.
|
|
18
|
-
|
|
19
|
-
Args:
|
|
20
|
-
nc_file (str): Path to the FlowFM NetCDF mesh file
|
|
21
|
-
"""
|
|
22
|
-
dataset = nc.Dataset(nc_file, 'r')
|
|
23
|
-
|
|
24
|
-
print(f"FlowFM Mesh Information from: {nc_file}")
|
|
25
|
-
print("=" * 60)
|
|
26
|
-
|
|
27
|
-
nodes = dataset.dimensions['Mesh2d_nNodes'].size
|
|
28
|
-
faces = dataset.dimensions['Mesh2d_nFaces'].size
|
|
29
|
-
edges = dataset.dimensions['Mesh2d_nEdges'].size
|
|
30
|
-
|
|
31
|
-
print(f"Number of mesh nodes: {nodes:,}")
|
|
32
|
-
print(f"Number of mesh faces: {faces:,}")
|
|
33
|
-
print(f"Number of mesh edges: {edges:,}")
|
|
34
|
-
|
|
35
|
-
if 'Mesh2d_face_nodes' in dataset.variables:
|
|
36
|
-
face_nodes = dataset.variables['Mesh2d_face_nodes'][:]
|
|
37
|
-
fill_value = dataset.variables['Mesh2d_face_nodes']._FillValue
|
|
38
|
-
valid_nodes = np.sum(face_nodes != fill_value, axis=1)
|
|
39
|
-
|
|
40
|
-
triangles = int(np.sum(valid_nodes == 3))
|
|
41
|
-
quads = int(np.sum(valid_nodes == 4))
|
|
42
|
-
|
|
43
|
-
print("\nElement Types:")
|
|
44
|
-
print(f" Triangular elements: {triangles:,}")
|
|
45
|
-
print(f" Quadrilateral elements: {quads:,}")
|
|
46
|
-
print(f" Total elements: {triangles + quads:,}")
|
|
47
|
-
|
|
48
|
-
if 'Mesh2d_node_x' in dataset.variables and 'Mesh2d_node_y' in dataset.variables:
|
|
49
|
-
x_coords = dataset.variables['Mesh2d_node_x'][:]
|
|
50
|
-
y_coords = dataset.variables['Mesh2d_node_y'][:]
|
|
51
|
-
|
|
52
|
-
print("\nSpatial extent:")
|
|
53
|
-
print(f" X range: {np.min(x_coords):.1f} to {np.max(x_coords):.1f}")
|
|
54
|
-
print(f" Y range: {np.min(y_coords):.1f} to {np.max(y_coords):.1f}")
|
|
55
|
-
|
|
56
|
-
dataset.close()
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def main():
|
|
60
|
-
parser = argparse.ArgumentParser(
|
|
61
|
-
prog='meshinfo',
|
|
62
|
-
description='Display mesh information from FlowFM NetCDF files',
|
|
63
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
64
|
-
epilog="""
|
|
65
|
-
Examples:
|
|
66
|
-
meshinfo # Use default FlowFM_net.nc
|
|
67
|
-
meshinfo grid.nc # Use any FlowFM mesh file
|
|
68
|
-
meshinfo -i grid.nc # Same, with the -i flag
|
|
69
|
-
meshinfo -h # Show this help message
|
|
70
|
-
|
|
71
|
-
The tool displays:
|
|
72
|
-
- Number of nodes, faces, and edges
|
|
73
|
-
- Element type distribution (triangles vs quadrilaterals)
|
|
74
|
-
- Spatial extent (X and Y coordinate ranges)
|
|
75
|
-
""",
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
parser.add_argument(
|
|
79
|
-
'file',
|
|
80
|
-
nargs='?',
|
|
81
|
-
metavar='FILE',
|
|
82
|
-
help='Path to the FlowFM NetCDF mesh file (default: FlowFM_net.nc)',
|
|
83
|
-
)
|
|
84
|
-
parser.add_argument(
|
|
85
|
-
'-i', '--input',
|
|
86
|
-
metavar='FILE',
|
|
87
|
-
help='Same as FILE (kept for backward compatibility)',
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
args = parser.parse_args()
|
|
91
|
-
|
|
92
|
-
if args.file and args.input:
|
|
93
|
-
parser.error('give the mesh file either as FILE or with -i, not both')
|
|
94
|
-
args.input = args.file or args.input or 'FlowFM_net.nc'
|
|
95
|
-
|
|
96
|
-
if not os.path.isfile(args.input):
|
|
97
|
-
print(f"Error: File '{args.input}' not found.")
|
|
98
|
-
print(f"Current directory: {os.getcwd()}")
|
|
99
|
-
print("Please check the file path and try again.")
|
|
100
|
-
sys.exit(1)
|
|
101
|
-
|
|
102
|
-
try:
|
|
103
|
-
show_mesh_info(args.input)
|
|
104
|
-
except Exception as e:
|
|
105
|
-
print(f"Error reading NetCDF file: {e}")
|
|
106
|
-
sys.exit(1)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if __name__ == "__main__":
|
|
110
|
-
main()
|
|
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
|