ncftools 0.6.0__tar.gz → 0.6.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ncftools
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: A collection of tools for working with NetCDF files
5
5
  Author-email: aaronchh <aaronhsu219@gmail.com>
6
6
  License-Expression: MIT
@@ -12,7 +12,7 @@ Description-Content-Type: text/markdown
12
12
  Requires-Dist: numpy>=1.20.0
13
13
  Requires-Dist: netCDF4>=1.6.0
14
14
  Requires-Dist: geopandas>=0.12.0
15
- Requires-Dist: shapely>=1.8.0
15
+ Requires-Dist: shapely>=2.0.0
16
16
 
17
17
  # NCFTOOLS
18
18
 
@@ -69,6 +69,9 @@ nc2shp -i mesh.nc -q
69
69
  Buffer triangle mesh faces by +1 m to form a transition zone, then select all faces that intersect it.
70
70
  Outputs `trans_zone.shp` and `trans_zone_extend.shp`.
71
71
 
72
+ Triangles are identified from the `type` attribute column when present; otherwise they are
73
+ detected automatically by vertex count (closed rings with 3 unique vertices).
74
+
72
75
  ```bash
73
76
  transzone1 -i SHP_NC/FlowFM_net_faces.shp
74
77
  transzone1 -i SHP_NC/FlowFM_net_faces.shp -o SHP_TRANS
@@ -53,6 +53,9 @@ nc2shp -i mesh.nc -q
53
53
  Buffer triangle mesh faces by +1 m to form a transition zone, then select all faces that intersect it.
54
54
  Outputs `trans_zone.shp` and `trans_zone_extend.shp`.
55
55
 
56
+ Triangles are identified from the `type` attribute column when present; otherwise they are
57
+ detected automatically by vertex count (closed rings with 3 unique vertices).
58
+
56
59
  ```bash
57
60
  transzone1 -i SHP_NC/FlowFM_net_faces.shp
58
61
  transzone1 -i SHP_NC/FlowFM_net_faces.shp -o SHP_TRANS
@@ -2,7 +2,7 @@
2
2
  NCFTOOLS - A collection of tools for working with NetCDF files.
3
3
  """
4
4
 
5
- __version__ = '0.6.0'
5
+ __version__ = '0.6.2'
6
6
 
7
7
  __all__ = [
8
8
  'meshinfo',
@@ -15,8 +15,7 @@ import sys
15
15
  import netCDF4 as nc
16
16
  import numpy as np
17
17
  import geopandas as gpd
18
- from shapely.geometry import Polygon
19
- from shapely.ops import unary_union
18
+ import shapely
20
19
 
21
20
 
22
21
  def read_mesh_netcdf(file_path, quiet=False):
@@ -71,65 +70,65 @@ def create_polygons(face_x, face_y, quiet=False):
71
70
  """
72
71
  Build Shapely Polygon objects from mesh face coordinate arrays.
73
72
 
73
+ Vectorized over all faces at once: the per-node validity mask, ring
74
+ coordinates, and polygon construction/repair are computed with numpy
75
+ and shapely's array API rather than a per-face Python loop.
76
+
74
77
  Returns:
75
78
  list[Polygon]
76
79
  """
77
80
  _print("Creating polygons from mesh faces...", quiet)
78
81
 
79
- polygons = []
80
- n_invalid = 0
81
- tri = quad = 0
82
-
83
- for i in range(len(face_x)):
84
- coords = [
85
- (float(face_x[i, j]), float(face_y[i, j]))
86
- for j in range(face_x.shape[1])
87
- if (not np.ma.is_masked(face_x[i, j])
88
- and not np.ma.is_masked(face_y[i, j])
89
- and abs(face_x[i, j]) < 1e30
90
- and abs(face_y[i, j]) < 1e30)
91
- ]
92
-
93
- if len(coords) < 3:
94
- n_invalid += 1
95
- continue
96
-
97
- if coords[0] != coords[-1]:
98
- coords.append(coords[0])
99
-
100
- try:
101
- poly = Polygon(coords)
102
- if not poly.is_valid or poly.is_empty:
103
- poly = poly.buffer(0)
104
- if poly.is_valid and not poly.is_empty:
105
- polygons.append(poly)
106
- n = len(coords) - 1 # exclude closing duplicate
107
- if n == 3:
108
- tri += 1
109
- elif n == 4:
110
- quad += 1
111
- else:
112
- n_invalid += 1
113
- except Exception as e:
114
- n_invalid += 1
115
- if not quiet:
116
- print(f" Warning: face {i} skipped: {e}")
82
+ x_data = np.ma.getdata(face_x).astype(np.float64)
83
+ y_data = np.ma.getdata(face_y).astype(np.float64)
84
+ valid = (
85
+ ~np.ma.getmaskarray(face_x)
86
+ & ~np.ma.getmaskarray(face_y)
87
+ & (np.abs(x_data) < 1e30)
88
+ & (np.abs(y_data) < 1e30)
89
+ )
90
+
91
+ n_valid_per_face = valid.sum(axis=1)
92
+ keep_face = n_valid_per_face >= 3
93
+
94
+ row_idx, col_idx = np.nonzero(valid & keep_face[:, None])
95
+ coords = np.column_stack((x_data[row_idx, col_idx], y_data[row_idx, col_idx]))
96
+
97
+ kept_face_ids = np.nonzero(keep_face)[0]
98
+ ring_id = np.full(len(keep_face), -1, dtype=np.int64)
99
+ ring_id[kept_face_ids] = np.arange(kept_face_ids.size)
100
+ ring_indices = ring_id[row_idx]
101
+
102
+ rings = shapely.linearrings(coords, indices=ring_indices)
103
+ polys = shapely.polygons(rings)
104
+
105
+ bad = ~shapely.is_valid(polys) | shapely.is_empty(polys)
106
+ if bad.any():
107
+ polys[bad] = shapely.buffer(polys[bad], 0)
108
+ bad = ~shapely.is_valid(polys) | shapely.is_empty(polys)
109
+
110
+ n_invalid = int(len(face_x) - keep_face.sum()) + int(bad.sum())
111
+ polys = polys[~bad]
112
+ node_counts = n_valid_per_face[kept_face_ids][~bad]
113
+
114
+ tri = int((node_counts == 3).sum())
115
+ quad = int((node_counts == 4).sum())
117
116
 
118
117
  if not quiet:
119
- print(f" Valid polygons: {len(polygons)} "
118
+ print(f" Valid polygons: {len(polys)} "
120
119
  f"(triangles: {tri}, quads: {quad}, skipped: {n_invalid})")
121
120
 
122
- return polygons
121
+ return list(polys)
123
122
 
124
123
 
125
124
  def create_geodataframe(polygons, crs="EPSG:3826"):
126
125
  """Build a GeoDataFrame from a list of Shapely polygons."""
127
126
  data = {
128
127
  'face_id': range(len(polygons)),
129
- 'area': [p.area for p in polygons],
128
+ 'area': shapely.area(polygons),
130
129
  'type': [
131
- 'triangle' if len(list(p.exterior.coords)) == 4
132
- else 'quadrilateral' if len(list(p.exterior.coords)) == 5
130
+ 'triangle' if len(p.exterior.coords) == 4
131
+ else 'quadrilateral' if len(p.exterior.coords) == 5
133
132
  else 'other'
134
133
  for p in polygons
135
134
  ],
@@ -140,7 +139,7 @@ def create_geodataframe(polygons, crs="EPSG:3826"):
140
139
  def dissolve_geodataframe(gdf, quiet=False):
141
140
  """Dissolve all polygons into a single geometry."""
142
141
  _print("Dissolving polygons...", quiet)
143
- geom = unary_union(gdf.geometry.tolist())
142
+ geom = shapely.union_all(gdf.geometry.values)
144
143
  dissolved = gpd.GeoDataFrame(
145
144
  {'id': [1], 'total_area': [geom.area], 'count': [len(gdf)]},
146
145
  geometry=[geom],
@@ -31,7 +31,15 @@ def build_transition_zone(faces_shp, output_dir="SHP_TRANS", quiet=False):
31
31
 
32
32
  gdf = gpd.read_file(faces_shp)
33
33
 
34
- triangles = gdf[gdf["type"] == "triangle"].copy()
34
+ if "type" in gdf.columns:
35
+ triangles = gdf[gdf["type"] == "triangle"].copy()
36
+ else:
37
+ # Detect triangles by vertex count (closed ring has 4 coords for 3 unique vertices)
38
+ is_triangle = gdf.geometry.apply(
39
+ lambda g: len(g.exterior.coords) == 4
40
+ )
41
+ triangles = gdf[is_triangle].copy()
42
+
35
43
  if triangles.empty:
36
44
  raise RuntimeError("No triangle faces found in attribute table.")
37
45
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ncftools
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: A collection of tools for working with NetCDF files
5
5
  Author-email: aaronchh <aaronhsu219@gmail.com>
6
6
  License-Expression: MIT
@@ -12,7 +12,7 @@ Description-Content-Type: text/markdown
12
12
  Requires-Dist: numpy>=1.20.0
13
13
  Requires-Dist: netCDF4>=1.6.0
14
14
  Requires-Dist: geopandas>=0.12.0
15
- Requires-Dist: shapely>=1.8.0
15
+ Requires-Dist: shapely>=2.0.0
16
16
 
17
17
  # NCFTOOLS
18
18
 
@@ -69,6 +69,9 @@ nc2shp -i mesh.nc -q
69
69
  Buffer triangle mesh faces by +1 m to form a transition zone, then select all faces that intersect it.
70
70
  Outputs `trans_zone.shp` and `trans_zone_extend.shp`.
71
71
 
72
+ Triangles are identified from the `type` attribute column when present; otherwise they are
73
+ detected automatically by vertex count (closed rings with 3 unique vertices).
74
+
72
75
  ```bash
73
76
  transzone1 -i SHP_NC/FlowFM_net_faces.shp
74
77
  transzone1 -i SHP_NC/FlowFM_net_faces.shp -o SHP_TRANS
@@ -1,4 +1,4 @@
1
1
  numpy>=1.20.0
2
2
  netCDF4>=1.6.0
3
3
  geopandas>=0.12.0
4
- shapely>=1.8.0
4
+ shapely>=2.0.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ncftools"
7
- version = "0.6.0"
7
+ version = "0.6.2"
8
8
  authors = [
9
9
  { name = "aaronchh", email = "aaronhsu219@gmail.com" },
10
10
  ]
@@ -16,7 +16,7 @@ dependencies = [
16
16
  "numpy>=1.20.0",
17
17
  "netCDF4>=1.6.0",
18
18
  "geopandas>=0.12.0",
19
- "shapely>=1.8.0",
19
+ "shapely>=2.0.0",
20
20
  ]
21
21
  classifiers = [
22
22
  "Programming Language :: Python :: 3",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes